<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; &#x62C;&#x627;&#x641;&#x627;</description><language>ar</language><item><title>&#x645;&#x642;&#x627;&#x631;&#x646;&#x629; &#x628;&#x64A;&#x646; &#x643;&#x648;&#x62A;&#x644;&#x646; &#x648;&#x62C;&#x627;&#x641;&#x627; &#x641;&#x64A; &#x645;&#x62C;&#x627;&#x644; &#x627;&#x644;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645;&#x627;&#x62A; &#x627;&#x644;&#x639;&#x627;&#x645;&#x629; &#x648;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x623;&#x646;&#x62F;&#x631;&#x648;&#x64A;&#x62F;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%83%D9%88%D8%AA%D9%84%D9%86-%D9%88%D8%AC%D8%A7%D9%81%D8%A7-%D9%81%D9%8A-%D9%85%D8%AC%D8%A7%D9%84-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A3%D9%86%D8%AF%D8%B1%D9%88%D9%8A%D8%AF-r2320/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_05/---------.png.d64605882e2cba8c1610629f08c0b637.png" /></p>
<p>
	لاشك أن أي مطور تطبيقات سمع بلغة البرمجة كوتلن Kotlin ولغة جافا Java فهما من لغات البرمجة القوية والمستخدمة بشكل كبير مع نظام التشغيل أندرويد، وبالرغم من أن لغة جافا قد خسرت معركة نظام أندرويد لصالح لغة كوتلن بعد أن أعلنت جوجل عام 2017 أن كوتلن هي اللغة الرسمية المعتمدة لنظام التشغيل أندرويد وأنها أكثر ملاءمةً لتطبيقات الهواتف المحمولة الجديدة، إلا أن كلا اللغتين تملكان العديد من نقاط القوة، ومن المهم للمطورين فهم الاختلافات اللغوية بينهما لتحقيق انتقال سهل من جافا إلى كوتلن.<br>
	سنناقش في هذا المقال مميزات كل لغة من هاتين اللغتين، ونسلط الضوء على أبرز جوانب التشابه والاختلاف بينهما لنساعدك على تحديد اللغة الأنسب لك من بينهما والانتقال بسلاسة بينهما.
</p>

<h2 id="-1">
	هل لغة كوتلن ولغة جافا متشابهتان؟
</h2>

<p>
	نعم، حيث تملك هاتان اللغتان الكثير من القواسم المشتركة، فكلتاهما تعملان على آلة جافا الافتراضية <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">JVM</a> ولن يحتاج المطورون للقلق بشأن كتابة الأكواد البرمجية الأصلية للآلة، ويمكن للغتين أن تستدعيا أكواد بعضهما البعض بسهولة-بمعنى أن بإمكانك استدعاء أكواد جافا من داخل أكواد كوتلن- والعكس صحيح.
</p>

<p>
	كما يمكن استخدام <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r2275/" rel="">لغة جافا</a> في تطبيقات <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-backend-web-development/" rel="">الواجهة الخلفية</a> التي تعمل من طرف الخادم وفي تطبيقات قواعد البيانات وتطبيقات الواجهة الأمامية والأنظمة المدمجة وتطبيقات الجوال وغيرها من أنواع التطبيقات المتنوعة الأخرى، وكذلك تتميز لغة كوتلن أيضًا بأنها لغة متعددة الاستخدامات فهي تعمل على أي نظام تشغيل يدعم آلة جافا الافتراضية <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">JVM</a> وتعد اللغة الرسمية لتطوير تطبيقات أندرويد، ويمكن كذلك استخدامها في <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA/" rel="">برمجة تطبيقات</a> جافا سكريبت من خلال مشروع Kotlin/JS الذي يسمح بكتابة شيفرات مشتركة بين اللغتين، ولتطوير تطبيقات من طرف الخادم، وتطبيقات الويب، و<a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%B3%D8%B7%D8%AD-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8/" rel="">تطبيقات سطح المكتب</a>.
</p>

<p>
	إن لغة جافا هي لغة أقدم وأكثر نضجًا من كوتلن، فقد صدرت لأول مرة في عام 1996، بينما أُطلِق أول إصدار من كوتلن Kotlin 1.0 في عام 2016، وبالرغم من ذلك تمكنت لغة كوتلن من فرض نفسها بسرعة وأصبحت اللغة الرسمية المفضلة لتطوير نظام التشغيل أندرويد في عام 2019، ويمكن القول بأننا إذا استثنينا مجال الأندرويد، فلن يكون هناك أي مميزات ترجح كفة كوتلن على جافا.
</p>

<p>
	وإليك الجدول التالي الذي يوضح تطور لغتي البرمجة Java و Kotlin:
</p>

<table>
	<thead>
		<tr>
			<th>
				<strong>السنة</strong>
			</th>
			<th>
				<strong>جافا</strong>
			</th>
			<th>
				<strong>كوتلن</strong>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1995–2006
			</td>
			<td>
				JDK Beta, JDK 1.0, JDK 1.1, J2SE 1.2, J2SE 1.3, J2SE 1.4, J2SE 5.0, Java SE 6
			</td>
			<td>
				لم تكن لغة كوتلن موجودة
			</td>
		</tr>
		<tr>
			<td>
				2007
			</td>
			<td>
				بدء العمل على مشروع Project Loom
			</td>
			<td>
				لم تكن لغة كوتلن موجودة بعد
			</td>
		</tr>
		<tr>
			<td>
				2010
			</td>
			<td>
				لم يطلق إصدار جديد من جافا
			</td>
			<td>
				بدء تطوير كوتلن
			</td>
		</tr>
		<tr>
			<td>
				2011
			</td>
			<td>
				Java SE 7
			</td>
			<td>
				الإعلان عن مشروع كوتلن
			</td>
		</tr>
		<tr>
			<td>
				2012
			</td>
			<td>
				لم يطلق إصدار جديد من جافا
			</td>
			<td>
				اعتبار لغة كوتلن مفتوحة المصدر
			</td>
		</tr>
		<tr>
			<td>
				2014
			</td>
			<td>
				Java SE 8 (LTS)
			</td>
			<td>
				لم يطلق إصدار جديد من كوتلن
			</td>
		</tr>
		<tr>
			<td>
				2016
			</td>
			<td>
				لم يطلق إصدار جديد من جافا
			</td>
			<td>
				Kotlin 1.0
			</td>
		</tr>
		<tr>
			<td>
				2017
			</td>
			<td>
				Java SE 9
			</td>
			<td>
				Kotlin 1.2 والإعلان عن دعم Kotlin لنظام أندرويد.
			</td>
		</tr>
		<tr>
			<td>
				2018
			</td>
			<td>
				Java SE 10, Java SE 11 (LTS)
			</td>
			<td>
				Kotlin 1.3 ودعم الروتينات المساعدة coroutines
			</td>
		</tr>
		<tr>
			<td>
				2019
			</td>
			<td>
				Java SE 12, Java SE 13
			</td>
			<td>
				Kotlin 1.4 (توافق مع Objective-C و Swift) والإعلان عن كوتلن كلغة مفضلة للمطورين من قبل جوجل.
			</td>
		</tr>
		<tr>
			<td>
				2020
			</td>
			<td>
				Java SE 14, Java SE 15
			</td>
			<td>
				لم يطلق إصدار جديد من كوتلن
			</td>
		</tr>
		<tr>
			<td>
				2021
			</td>
			<td>
				Java SE 16, Java SE 17 (LTS)
			</td>
			<td>
				Kotlin 1.5, Kotlin 1.6
			</td>
		</tr>
		<tr>
			<td>
				2022
			</td>
			<td>
				Java SE 18, JDK 19
			</td>
			<td>
				Kotlin 1.7 (alpha version of Kotlin K2 compiler), Kotlin 1.8
			</td>
		</tr>
		<tr>
			<td>
				2023
			</td>
			<td>
				Java SE 20, Java SE 21, JDK 20, JDK 21
			</td>
			<td>
				Kotlin 1.9
			</td>
		</tr>
		<tr>
			<td>
				2024
			</td>
			<td>
				Java SE 22
			</td>
			<td>
				Kotlin 2.0
			</td>
		</tr>
	</tbody>
</table>

<h2>
	مقارنة بين كوتلن وجافا من ناحية الأداء واستهلاك الذاكرة
</h2>

<p>
	قبل الخوض في تفاصيل مميزات لغتي كوتلن وجافا، لنقارن أداءهما واستهلاكما للذاكرة حيث يشكل هذان العاملان اعتبارين مهمين لكل من المطورين والمستخدمين على حد سواء.
</p>

<h3 id="-2">
	أولًا: من ناحية الأداء
</h3>

<p>
	على الرغم من أن كوتلن و جافا ولغات البرمجة الأخرى التي تنفذ على الآلة الافتراضية JVM الأخرى مختلفة في عدة عوامل عن بعضها، إلا أنها متشابهة إلى حد ما في الأداء، لا سيما عند مقارنتها مع عائلة اللغات المعتمدة على المصرّفات والتي تنفذ على المعالج الخاص بالآلة مثل مُصرّف GCC أو Clang ، فقد صممت آلة جافا الافتراضية JVM في الأصل لاستهداف <a href="https://academy.hsoub.com/programming/os-embedded-systems/" rel="">الأنظمة المدمجة</a> محدودة الموارد التي كانت متوفرة في فترة التسعينيات، وهذا فرض قيدين أساسيين على هذه اللغات لتتمكن من العمل بكفاءة على موارد محدودة وهما:
</p>

<ul>
	<li>
		<p>
			جعل شيفرة البايت لآلة جافا الافتراضية بسيطة قد المستطاع: إذ يحتوي الإصدار الحالي من آلة جافا الافتراضية JVM التي تترجم لغتي كوتلن وجافا، على 205 تعليمة فقط، في حين يمكن للمعالجات الحديثة x64 أن تدعم وتنفذ أكثر من 6000 تعليمة مشفرة اعتمادًا على طريقة العد.
		</p>
	</li>
	<li>
		<p>
			التحسين في وقت التشغيل Runtime: فأسلوب تعدد المنصات الذي يعتمد على كتابة الكود مرة واحدة وتشغيله في أي مكان يساعد على إجراء عمليات تحسين الأداء في وقت تشغيل الكود (وليس في وقت تصريفه compile-time)، بعبارة أخرى تقوم آلة جافا الافتراضية JVM بترجمة معظم شيفرة البايت الخاصة بها إلى تعليمات في وقت التشغيل، فعند ترجمة كود جافا أو كوتلن لا يحول هذا الكود مباشرةً إلى كود الآلة بل يحول إلى كود بايت byte code وسيط ثم تترجم الآلة الافتراضية هذا الكود الوسيط إلى تعليمات يمكن للجهاز فهمها أثناء تشغيل البرنامج، وبالتالي تؤجل بعض عمليات التحسين لحين تشغيل البرنامج بشكل فعلي. كما يمكن استخدام تطبيقات مفتوحة المصدر لآلة جافا الافتراضية JVM مثل HotSpot لتحسين الأداء إذ تقوم في هذه الحالة بتجهيز كود بايت مسبق الترجمة pre-compiles لتشغيله بشكل أسرع من خلال المفسر interpreter.
		</p>
	</li>
</ul>

<p>
	فكما ترى تتبع كل من جافا وكوتلن أساليب متشابهة لترجمة وتشغيل التعليمات البرمجية لذا لا توجد سوى اختلافات طفيفة في الأداء بينهما سببها بعض الخصائص الفريدة المميزة لكل لغة، على سبيل المثال:
</p>

<ul>
	<li>
		<p>
			تتجنب لغة كوتلن استدعاء الدوال باستخدام الدوال المباشرة <a href="https://wiki.hsoub.com/Kotlin/inline_functions" rel="external">inline functions</a> مما يحسن أداءها، بينما تعتمد جافا على الدوال التي تتطلب تخصيص ذاكرة إضافية.
		</p>
	</li>
	<li>
		<p>
			تتجنب كوتلن استخدام <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%B9%D9%84%D9%8A%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1242/" rel="">الدوال العليا higher-order functions</a> التي تستخدمها جافا لامدا Java lambda في الاستدعاء الديناميكي للدالة من خلال التعليمة <code>InvokeDynamic</code> مما يحسن الأداء.
		</p>
	</li>
	<li>
		<p>
			تحتوي شيفرة كود البايت bytecode التي تولدها لغة كوتلن على ميزة التحقق من القيم الفارغة عند استخدام التبعيات الخارجية، مما يبطئ أداءها مقارنةً بجافا.
		</p>
	</li>
</ul>

<h3 id="-3">
	ثانيًا: من ناحية الذاكرة
</h3>

<p>
	صحيح أن استخدام الكائنات للأنواع الأساسية (وهو الأسلوب المتبع في لغة كوتلن) يتطلب تخصيصًا أكثر للذاكرة مقارنة باستخدام أنواع البيانات الأولية (وهو الأسلوب المتبع في لغة جافا) من الناحية النظرية، لكن من الناحية العملية تستخدم تعليمات كود البايت في لغة جافا تقنية التغليف التلقائي autoboxing وفك التغليف unboxing للتعامل مع الكائنات والتي يمكن أن تضيف حملًا وعبئًا حسابيًا عند استخدامها بشكل مفرط. على سبيل المثال، لا تقبل الدالة <code>String.format</code> الخاصة بجافا سوى الكائنات كدخل لها، لذلك لذلك عند الحاجة لتنسيق عدد صحيح من نوع <code>int</code> في جافا، يتم تغليفه تلقائيًا في كائن من نوع <code>Integer</code> قبل استدعاء الدالة <code>String.format</code>.
</p>

<p>
	ويمكن القول بشكل عام، لا توجد اختلافات كبيرة بين لغتي جافا وكوتلن فيما يتعلق بالأداء والذاكرة، وبالرغم من أنك قد تجد معايير أداء مختلفة عبر الإنترنت تظهر اختلافات طفيفة في الاختبارات الدقيقة بين هاتين اللغتين، ولكن لا يمكن تعميم هذه النتائج.
</p>

<p>
	والجدير بالملاحظة أن الأداء واستهلاك الذاكرة لا ينبغي أن يكونا العاملين الأساسيين في اختيارك بين كوتلن أو جافا ومن الأفضل التركيز على عوامل أخرى مثل المميزات الخاصة بكل لغة.
</p>

<h2 id="-4">
	مقارنة الميزات الفريدة لكل من لغتي كوتلن وجافا
</h2>

<p>
	تشترك لغتا كوتلن وجافا في العديد من السمات الأساسية، لكن كل لغة تقدم ميزات فريدة ومختلفة عن الأخرى، فمنذ أن أصبحت كوتلن لغة جوجل المفضلة لتطوير أندرويد، برزت ميزات مثل الدوال الإضافية وإمكانية تعيين القيم null بشكل صريح Explicit nullability بكونها الميزات الأكثر فائدة للغة، من ناحية أخرى عند استخدام كوتلن فإن أكثر الميزات التي سنفتقدها والتي كانت تتيحها لغة جافا هي الكلمة المفتاحية protected والمعامل الشرطي الثلاثي ternary operator الذي يمكننا من التعبير عن الشروط بطريقة قصيرة ومختصرة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="149231" href="https://academy.hsoub.com/uploads/monthly_2024_05/---.png.320c15ba216a038f0d0ed326d1d1eaa2.png" rel=""><img alt="المعامل-الشرطي-الثلاثي-معرب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="149231" data-ratio="34.60" data-unique="tvkja8th8" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_05/---.thumb.png.4351efc7f211b0668257c7f10a2c6677.png"></a>
</p>

<ul>
	<li>
		لنقم بتحليل مفصل للمميزات المتاحة في كولن مقارنةً بجافا، يمكنك تنفيذ الأمثلة التالية المكتوبة بلغة كوتلن وجافا لفهم الفروقات بشكل عملي.
	</li>
</ul>

<p>
	إليك بعض الميزات البارزة في لغة كوتلن بالمقارنة مع لغة جافا:
</p>

<table>
	<thead>
		<tr>
			<th>
				<strong>الخاصية</strong>
			</th>
			<th>
				<strong>كوتلن</strong>
			</th>
			<th>
				<strong>جافا</strong>
			</th>
			<th>
				<strong>الوصف</strong>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				الدوال الإضافية Extension functions
			</td>
			<td>
				نعم
			</td>
			<td>
				لا
			</td>
			<td>
				تسمح لك بتوسيع صنف أو واجهة بوظائف جديدة من خلال تعريف التوابع أو الخصائص الإضافية فقط دون الحاجة إلى إنشاء أصناف جديدة<br>
				<code>class Example {}&lt;br /&gt;// extension function declaration fun Example.printHelloWorld() { println("Hello World!") }&lt;br /&gt;&lt;br /&gt;// extension function usage Example().printHelloWorld()</code>
			</td>
		</tr>
		<tr>
			<td>
				التحويلات الذكية Smart casts
			</td>
			<td>
				نعم
			</td>
			<td>
				لا
			</td>
			<td>
				تتبع الشروط داخل عبارة if الشرطية، وتحويل الأنواع تلقائياً بطريقة آمنة.<code>&lt;br /&gt;&lt;br /&gt; fun example(a: Any) { if (a is String)&lt;br /&gt; { println(a.length) &lt;br /&gt;// automatic cast to String } }</code><br>
				كما توفر لغة كوتلن معاملات تحويل آمنة بين الأنواع تعيد <code>null</code> عند حدوث خطأ في عملية التحويل ومعاملات تحويل غير آمنة ترمي استثناء عند حدوث خطأ في التحويل<br>
				<code>// unsafe "as" cast throws exceptions val a: String = b as String // safe "as?" cast returns null on failure val <span class="ipsEmoji">?</span> String? = d as? String</code>
			</td>
		</tr>
		<tr>
			<td>
				الدوال المباشرة Inline functions
			</td>
			<td>
				نعم
			</td>
			<td>
				لا
			</td>
			<td>
				تقلل من التحميل الزائد للذاكرة وتحسن سرعة الكود عن طريق تضمين رمز الدالة (نسخه إلى موقع الاستدعاء)<br>
				<code>inline fun example()</code>
			</td>
		</tr>
		<tr>
			<td>
				دعم مدمج لمفهوم التفويض delegation
			</td>
			<td>
				نعم
			</td>
			<td>
				لا
			</td>
			<td>
				تدعم نمط التفويض بشكل مدمج باستخدام الكلمة المفتاحية <code>by</code><br>
				<code>class Derived(b: Base) : Base by b</code>
			</td>
		</tr>
		<tr>
			<td>
				كتابة أسماء مستعارة أو بديلة لأنماط البيانات
			</td>
			<td>
				نعم
			</td>
			<td>
				نعم
			</td>
			<td>
				توفر أسماء مختصرة أو مخصصة لأنماط البيانات والتوابع والأصناف لتسهيل قراءتها<br>
				<code>typealias ShortName = LongNameExistingType</code>
			</td>
		</tr>
		<tr>
			<td>
				الحقول غير الخاصة بالصنف
			</td>
			<td>
				لا
			</td>
			<td>
				نعم
			</td>
			<td>
				توفر جافا كافة معاملات تعديل الوصول الأربعة وهي المحمي <code>protected</code> والافتراضي default المعروف أيضًا بمعامل الحزمة الخاص <code>package-private</code> بالإضافة إلى معامل الوصول العام <code>public</code> والخاص <code>private</code>. بينما تفتقد كوتلن لمعامل تعديل الوصول المحمي والافتراضي.
			</td>
		</tr>
		<tr>
			<td>
				العامل الثلاثي Ternary operator
			</td>
			<td>
				لا
			</td>
			<td>
				نعم
			</td>
			<td>
				يستبدل عبارة if/else بكود أبسط وأكثر قابلية للقراءة:<br>
				<code>if (firstExpression) { // if/else variable = secondExpression; } else { variable = thirdExpression; } // ternary operator variable = (firstExpression) ? secondExpression : thirdExpression</code>;
			</td>
		</tr>
		<tr>
			<td>
				توسيع نطاق البيانات بصورة ضمنية
			</td>
			<td>
				لا
			</td>
			<td>
				نعم
			</td>
			<td>
				تسمح لغة جافا بالتحويل التلقائي من نوع بيانات أصغر إلى نوع بيانات أكبر مثلًا من عدد صحيح إلى عشري بصورة تلقائية في حين لا تقوم كوتلن بذلك<br>
				<code>int i = 10; long l = i; // first widening conversion: int to long float f = l; // second widening conversion: long to float</code>
			</td>
		</tr>
		<tr>
			<td>
				الاستثناءات المتحقق منها<br>
				Checked exceptions
			</td>
			<td>
				لا
			</td>
			<td>
				نعم
			</td>
			<td>
				يحتاج المصرف إلى طريقة للتعامل مع الاستثناءات التي تقع عند تنفيذ البرنامج إما باستخدام الكلمة المفتاحية <code>throws</code> أو من خلال كتلة تعليمات <code>try-catch</code><br>
				ملاحظة: تم تصميم الاستثناءات التي يجب التحقق منها لتشجيع المطورين على تصميم برمجيات قوية. ومع ذلك، يمكن أن تؤدي إلى هذه الاستثناءات لزيادة حجم الكود، وتجعل عملية إعادة التصميم صعبة، وتؤدي إلى معالجة سيئة للأخطاء عند استخدامها بطريقة غير صحيحة. لذا فإن مسألة تحديد فيما إذا كانت هذه الميزة إيجابية أم سلبية تعتمد على احتياجات المطورين.
			</td>
		</tr>
	</tbody>
</table>

<p>
	هناك موضوع واحد تم استبعاده عمدًا من هذا الجدول وهو التعامل مع القيم الفارغة null في كل من لغتي كوتلن وجافا. إذ يستحق هذا الموضوع مقارنة أكثر تفصيلاً بين اللغتين لذا سنناقشه بشكل منفصل في الفقرة التالية.
</p>

<h2 id="null">
	الفرق بين كوتلن وجافا في التعامل مع القيم الفارغة null
</h2>

<p>
	إن ميزة عدم قبول القيم الفارغة هي واحدة من أروع ميزات كوتلن. إذ توفر هذه الميزة الوقت على المطورين ولا تضطرهم للتعامل مع الاستثناء من نوع NullPointerExceptions (وهو أحد الاستثناءات التي تطلق في وقت التشغيل RuntimeExceptions).
</p>

<p>
	ففي لغة جافا يمكنك بشكل افتراضي تعيين قيمة فارغة لأي متغير على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2988_15" style=""><span class="typ">String</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">

</span><span class="com">// Running this code throws a NullPointerException</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"First character: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> x</span><span class="pun">.</span><span class="pln">charAt</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">NullPointerException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"NullPointerException thrown!"</span><span class="pun">);</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	أما في لغة كوتلن فلديك خياران، إما تجعل المتغير يقبل قيم فارغة nullable أو لا يقبل قيم فارغة non-nullable كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2988_21" style=""><span class="com">// This line throws a compile-time error because you can't assign a null value</span><span class="pln">

nonNullableNumber </span><span class="pun">=</span><span class="pln"> null

var nullableNumber</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Int</span><span class="pun">?</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

 </span><span class="com">// This line does not throw an error since we used a nullable variable</span><span class="pln">

nullableNumber </span><span class="pun">=</span><span class="pln"> null</span></pre>

<p>
	يفضل استخدام المتغيرات non-nullable بشكل افتراضي، وتجنب استخدام المتغيرات nullable للحصول على أفضل الممارسات.
</p>

<p>
	<strong>ملاحظة</strong>: الهدف من الأكواد المكتوبة بكوتلن ومقارنتها مع أكواد مكتوبة بلغة جافا هو توضيح الاختلافات بين اللغتين وفي حال كنت مبتدئًا في لغة كوتلن تجنب قدر المستطاع تعيين المتغيرات لتكون nullable بدون هدف أو عند تحويل كود جافا إلى كوتلن. ومع ذلك، هناك بعض الحالات التي قد تستخدم فيها المتغيرات التي تقبل القيم null في كوتلن ومن بين هذه الحالات نذكر:
</p>

<table>
	<thead>
		<tr>
			<th>
				<strong>الحالة</strong>
			</th>
			<th>
				<strong>مثال</strong>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				عندما تبحث عن عنصر في قائمة غير موجود (عادةً عند التعامل مع بنية لتخزين ومعالجة البيانات).
			</td>
			<td>
				<code>val list: List&lt;Int&gt; = listOf(1,2,3) val searchResultItem = list.firstOrNull { it == 0 } searchResultItem?.let { // Item found, do something } ?: run { // Item not found, do something }</code>
			</td>
		</tr>
		<tr>
			<td>
				عندما تريد تهيئة متغير في وقت التنفيذ وليس في لحظة الإعلان عنه باستخدام lateinit.
			</td>
			<td>
				<code>lateinit var text: String fun runtimeFunction() { // e.g., Android onCreate text = "First text set" // After this, the variable can be used }</code>
			</td>
		</tr>
	</tbody>
</table>

<p>
	ينصح بعدم المبالغة في استخدام التعليمة lateinit التي تؤجل تهيئة المتغير حتى يتم الوصول إليه لأول مرة بشكل مفرط مع لغة كوتلن باستثناء الحالات التي لا يمكنك فيها معرفة قيمة المتغير عند إعلانه، أو عند الحاجة لربط العرض view bindings أي ربط عناصر واجهة المستخدم (مثل الأزرار والنصوص والصور وما إلى ذلك) بمتغيرات في الشيفرة البرمجية، كي تتمكن من لتحكم فيها والتفاعل معها من خلال العرض وإجراء حقن قيم للمتغيرات في وقت التشغيل variable injections في أندرويد.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2988_19" style=""><span class="lit">@Inject</span><span class="pln"> </span><span class="com">// With the Hilt library, this is initialized automatically</span><span class="pln">

lateinit var manager</span><span class="pun">:</span><span class="pln"> </span><span class="typ">SomeManager</span><span class="pln">

lateinit var viewBinding</span><span class="pun">:</span><span class="pln"> </span><span class="typ">ViewBinding</span><span class="pln">

fun onCreate</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// i.e., Android onCreate</span><span class="pln">

 binding </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ActivityMainBinding</span><span class="pun">.</span><span class="pln">inflate</span><span class="pun">(</span><span class="pln">layoutInflater</span><span class="pun">,</span><span class="pln"> parentView</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln">

 </span><span class="com">// ...</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	بشكل عام، تتعامل لغة كوتلن مع القيم الفارغة null بمرونة أكبر وتوفر تجربةً محسنةً مقارنةً بلغة جافا.
</p>

<h2 id="-5">
	الاختلافات في الميزات المشتركة بين جافا وكوتلن
</h2>

<p>
	على الرغم من أن كل لغة لها ميزات فريدة، إلا أن كوتلن و جافا تشتركان في العديد من الميزات أيضًا، ومن الضروري فهم خصوصية كل لغة من أجل الانتقال بسلاسة بين اللغتين. لنقم بفحص أربعة مفاهيم مشتركة بين جافا وكوتلن لكنها تعمل بشكل مختلف في كل لغة:
</p>

<table>
	<thead>
		<tr>
			<th>
				<em>الميزة</em>
			</th>
			<th>
				<em>جافا</em>
			</th>
			<th>
				<em>كوتلن</em>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				كائنات نقل البيانات بين طبقات التطبيق المختلفة DTOs
			</td>
			<td>
				توفر سجلات جافا Java records طريقة مناسبة لنقل المعلومات عن البيانات أو حالة هذه البيانات كما تتضمن التوابع المساعدة toString و equals و hashCode بشكل افتراضي وهي متاحة منذ إصدار جافا SE 15:<br>
				<br>
				<br>
				public record Employee(<br>
				int id,<br>
				String firstName,<br>
				String lastName<br>
				)
			</td>
			<td>
				توفر لغة كوتلن أصناف البيانات data classes وهي تعمل بشكل مشابه لسجلات جافا كما تتوفر فيها توابع مدمجة مساعدة toString و equals و<code>copy</code>:<br>
				<br>
				<br>
				data class Employee(<br>
				val id: Int,<br>
				val firstName: String,<br>
				val lastName: String<br>
				)
			</td>
		</tr>
		<tr>
			<td>
				تعبيرات لامدا Lambda expressions
			</td>
			<td>
				تتوفر تعبيرات لامدا في جافا منذ الإصدار جافا 8 وهي تملك بنية بسيطة كالتالي parameter -&gt; expression<code>وتستخدم القوسين المتعرجين لتمرير الوسطاء(parameter1, parameter2) -&gt; { code }</code> كما في المثال التالي الذي يستخدم تعبيرات لامدا لصباعة عناصر قائمة أعداد صححية:<br>
				<br>
				<br>
				ArrayList<integer> ints = new ArrayList&lt;&gt;();<br>
				ints.add(5);<br>
				ints.add(9);<br>
				ints.forEach((i) -&gt; {<br>
				System.out.println(i);<br>
				});</integer>
			</td>
			<td>
				تكتب تعبيرات لامدا في كوتلن بالصيغة التالية { parameter1, parameter2 -&gt; code } وتحيط بها دائمًا الأقواس المتعرجة<br>
				على سبيل المثال يستخدم الكود التالي تعبير لامدا لتعريف دالة بسيطة تقارن طول سلسلتين نصيتين: var p: List<string> = listOf("firstPhrase", "secondPhrase") val isShorter = { s1: String, s2: String -&gt; s1.length &lt; s2.length } println(isShorter(p.first(), p.last()))</string>
			</td>
		</tr>
		<tr>
			<td>
				التزامن Concurrency
			</td>
			<td>
				توفر الخيوط threads في لغة جافا ميزة تنفيذ مهام متزامنة وتسهل الحزمة java.util.concurrency التعامل مع تعدد المهام من خلال توفير أصناف خاصة مثل Executor و ExecutorService (كما يمكن مشروع Project Loom لتحسين جافا من إنجاز التزامن بطريقة أسرع من خلال توفير خيوط خفيفة الوزن lightweight threads).
			</td>
			<td>
				تعمل الروتينات المساعدة في كوتلن التي توفرها مكتبة kotlinx.coroutines على تسهيل التزامن وتعدد المهام، كما تتضمن قسم خاص لتعدد الخيوط multithreading. ويساهم مدير الذاكرة في إصدار Kotlin 1.7.20 والإصدارات الأحدث بتقليل القيود السابقة على تنفيذ التزامن وتعدد الخيوط للمطورين الذين ينتقلون بين نظام التشغيل أندرويد Android وآي أو إس iOS.
			</td>
		</tr>
		<tr>
			<td>
				السلوك الثابت في الأصناف Static behavior in classes
			</td>
			<td>
				تسهل الأعضاء الثابتة أو الستاتيكية static members في جافا مشاركة التعليمات البرمجية بين كائنات الصنف وتضمن إنشاء نسخة واحدة فقط من العنصر. ويمكن تحقيقها من خلال كتابة الكلمة المفتاحية static قبل المتغيرات والدوال والكتل: class Example { static void f() {/<em>…</em>/} }
			</td>
			<td>
				تتيح الكائنات المرافقة للأصناف companion objects في كوتلن من تحقيق السلوك الثابت للأصناف ومشاركة التعليمات البرمجية بين كائنات الصنف فالأعضاء في الكائن المرافق تكون مشتركة بين جميع كائنات الصنف ولا حاجة لإنشاء كائن منه لاستخدامها، ولكن صياغتها ليست بسيطة كما هو الحال في جافا:<br>
				إليك مثال على كيفية استخدام كائن مرفق في كوتلن:\ class Example { companion object { fun f() {/<em>…</em>/} } }
			</td>
		</tr>
	</tbody>
</table>

<p>
	بالطبع، تملك كل من لغة البرمجة كوتلن وجافا أيضًا تراكيب مختلفة في كتابة التعليمات البرمجية، ولن نناقش كل الاختلافات هنا في سياق المقال الحالي، لكننا سنكتفي بالاطلاع على طريقة كتابة الحلقات التكرارية في كل لغة وهذا سيمنحك فكرة عن الأسلوب العام لكتابة تعليمات كل لغة:
</p>

<table>
	<thead>
		<tr>
			<th>
				<strong>نوع الحلقة</strong>
			</th>
			<th>
				<strong>جافا</strong>
			</th>
			<th>
				<strong>كوتلن</strong>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				حلقة for باستخدام in
			</td>
			<td>
				<code>for (int i=0; i&lt;=5; i++) { System.out.println("printed 6 times"); }</code>
			</td>
			<td>
				<code>for (i in 0..5) { println("printed 6 times") }</code>
			</td>
		</tr>
		<tr>
			<td>
				حلقة for باستخدام until
			</td>
			<td>
				<code>for (int i=0; i&lt;5; i++) { System.out.println("printed 5 times"); }</code>
			</td>
			<td>
				<code>for (i in 0 until 5) { println("printed 5 times") }</code>
			</td>
		</tr>
		<tr>
			<td>
				forEach
			</td>
			<td>
				<code>List&lt;String&gt; list = Arrays.asList("first", "second"); for (String value: list) { System.out.println(value); }</code>
			</td>
			<td>
				<code>var list: List&lt;String&gt; = listOf("first", "second") list.forEach { println(it) }</code>
			</td>
		</tr>
		<tr>
			<td>
				while
			</td>
			<td>
				<code>int i = 5; while (i &gt; 0) { System.out.println("printed 5 times"); i--; }</code>
			</td>
			<td>
				<code>var i = 5 while (i &gt; 0) { println("printed 5 times") i-- }</code>
			</td>
		</tr>
	</tbody>
</table>

<p>
	احرص على الفهم العميق لميزات لغة كوتلن فهذا سيساعدك في التحول بين كوتلن وجافا بسهولة وسلاسة
</p>

<h2 id="-6">
	هل أختار جافا أم كوتلن من أجل مشاريع أندرويد
</h2>

<p>
	لقد ناقشنا حتى الآن العديد من العوامل المهمة التي يجب التفكير فيها عند اتخاذ القرار بين كوتلن وجافا في من وجهة نظر عامة، لكن لا تكتمل عملية المقارنة بين لغتي كوتلن وجافا إذا لم نتطرق للحديث عن نظام أندرويد. فإذا كنت تنوي تطوير تطبيق أندرويد من الصفر وتتساءل ما إذا كان يجب عليك أن تستخدم جافا أو كوتلن فينصح بأن تختار كوتلن بدون أي تردد فهي لغة الأندرويد المفضلة لدى جوجل كما ذكرنا سابقًا.
</p>

<p>
	ورغم ذلك، فإن الإجابة على هذا السؤال قد يخضع لاعتبارات أخرى بالنسبة لتطبيقات الأندرويد المطورة مسبقًا. إذا يجب عليك أيضًا مناقشة أمور إضافية مثل مسألة <a href="https://wiki.hsoub.com/Refactoring/technical_debt" rel="external">الأعباء التقنية</a> والكلفة المرتبطة بتعديل التقنية المستخدمة في هذه التطبيقات من لغة جافا إلى كوتلن وخبرة المطور Developer Experience الذي يعرف من قبل جافا ويتوجب عليه تعلم لغة جديدة!
</p>

<p>
	فإذا كان لدى شركة ما تطبيق أندرويد يستخدم جافا، فمن المستبعد أن تعيد برمجته من جديد بكوتلن بل ستفضل إضافة ميزات جديدة له فقط، وهذا أمر مفهوم. فطبيعة السوق تنافسية وتتطلب دورة سريعة لتحديثات التطبيق. لكن يجب الانتباه فالعبء التقني له تأثير خفي، فهو يزيد من التكاليف مع كل تحديث لأن المهندسين يضطرون للتعامل مع الشيفرة غير المستقرة التي من الصعب إعادة تنظيمها، وقد يدخل الشركات في دورة لا نهائية من التكاليف الباهظة الناتجة عن كثرة عمليات التعديل. وقد يكون من الأجدى التوقف عن تعديل الأكواد القديمة والاستثمار في حلول طويلة الأمد، حتى لو تطلب ذلك عمليات إعادة كتابة الكود البرمجي من جديد أو تحديث كود البرنامج لاستخدام لغة حديثة مثل كوتلن.
</p>

<p>
	كما أن استخدام لغة برمجة جديدة من قبل المطورين أمر يستحق النقاش حيث يمكن أن يفيد المطورين باختلاف خبراتهم إذ:
</p>

<ul>
	<li>
		يستفيد المطورون المبتدئون من الموارد المناسبة.
	</li>
	<li>
		يتحسن المطورون ذوو المستوى المتوسط ويحصلون على فرص أكبر للعمل.
	</li>
	<li>
		يحتاج المطورون الكبار إلى القدرة على تصميم وتنفيذ شيفرات رائعة.
	</li>
</ul>

<p>
	ولا شك أن الاهتمام بتجربة المطورين الخبراء مهم وضروري لاسيما بأن خبرتهم تؤثر على جميع المطورين ويمكن باستخدام لغات حديثة مثل كوتلن تطوير التطبيقات بسرعة وكفاءة وسهولة وهو ما سيستغرق وقتًا أطول مع لغات أقدم مثل جافا.
</p>

<h2 id="-7">
	الخلاصة
</h2>

<p>
	تعرفنا في مقال اليوم على أهم الفروقات بين لغتي كوتلن و جافا اللتان تعدان من لغات البرمجة القوية والفعالة، ووجدنا أنه وبالرغم من أن لغة جافا لديها مجموعة واسعة من التطبيقات، فقد استحوذت كوتلن على المكانة التي امتلكتها لغة جافا والتي كانت لغة مفضلة لتطوير تطبيقات الأندرويد فقد وجهت جوجل كل جهودها نحو تطوير ودعم كوتلن، ما جعلها تحظى بالأولوية أمام جافا. لذا ينصح أي مطور تطبيقات باعتماد لغة كوتلن في أي شيفرة جديدة يكتبها لا سيما أن بيئة تطوير تطبيقات جافا IntelliJ IDEA تأتي مع أداة تحويل تلقائي من لغة جافا إلى كوتلن.
</p>

<p>
	ترجمة وبتصرف للمقال <a href="https://www.toptal.com/kotlin/kotlin-vs-java" rel="external nofollow">Kotlin vs. Java: All-purpose Uses and Android Apps</a> للكاتب Gabriel Gircenko
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r2275/" rel="">تعرف على لغة جافا Java</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/kotlin/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-kotlin-r603/" rel="">الدليل السريع إلى لغة البرمجة Kotlin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/kotlin/kotlin-%D9%87%D9%88-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%AC%D8%AF%D9%8A%D8%AF-r641/" rel="">Kotlin هو جافا الجديد</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1308/" rel="">كيفية كتابة برامج صحيحة باستخدام لغة جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2320</guid><pubDate>Tue, 07 May 2024 12:02:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x644;&#x63A;&#x629; &#x62C;&#x627;&#x641;&#x627; Java</title><link>https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r2275/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_03/---.png.242a65140692559a800f3eee44dec951.png" /></p>
<p>
	نعرفك في مقال اليوم على لغة جافا Java إحدى لغات البرمجة العريقة عامة الأغراض وذائعة الصيت بين أوساط المطورين، ونكتشف أبرز مميزاتها وعيوبها ومجالات استخداماتها، ونختم المقال بمجموعة من النصائح والخطوات التي تسهل عليك تعلم الجافا من الصفر حتى الاحتراف.
</p>

<h2 id="">
	ما هي لغة جافا
</h2>

<p>
	لغة جافا Java هي لغة برمجة عامة الأغراض أطلقتها شركة صن ميكروسيستمز Sun Microsystems عام 1995، وكان الهدف من تطويرها في الأساس إنشاء لغة برمجة محمولة ومستقلة عن نظام التشغيل فخرجوا لنا بلغة البرمجة جافا Java التي اقتبس اسمها من جزيرة جافا المشهورة بإنتاج القهوة وهي كما تعرف المشروب المفضل لدى معشر المبرمجين، كما أن شعار اللغة يأخذ شكل فنجان من القهوة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_09/--.png.d02492fc89a83f02dbb1140131cdfae8.png" data-fileid="157297" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="157297" data-ratio="65.67" data-unique="a5rck6vjp" style="width: 300px; height: auto;" width="900" alt="شعار-لغة-جافا.png" src="https://academy.hsoub.com/uploads/monthly_2024_09/--.thumb.png.bbfdf0af20467f01e8675bb8f6836dfd.png"></a>
</p>

<p>
	وفي عام 2009 استحوذت شركة أوراكل Oracle على شركة Sun Microsystems فأصبحت هي المالك الفعلي للغة البرمجة جافا. وعملت على تطوير إصدارات مختلفة من اللغة و<a href="https://en.wikipedia.org/wiki/Java_version_history" rel="external nofollow">أحدث إصدار من جافا</a> عند كتابة هذا المقال هو Java SE 21 الصادر في سبتمبر عام 2023، وهو إصدار طويل الدعم LTS (سينتهي دعمه الرسمي في سبتمبر 2028) وقد فرضت لغة جافا نفسها اليوم كإحدى أشهر وأقوى لغات البرمجة في العالم، كما أنها من لغات البرمجة الأعلى طلبًا في سوق العمل وهي تستخدم في العديد من المجالات والتطبيقات التي سنتعرف عليها في فقرات لاحقة.
</p>

<h2 id="jvm">
	ما أهمية آلة جافا الوهمية JVM للغة البرمجة جافا؟
</h2>

<p>
	تعتمد لغة جافا في تصميمها على مبدأ الكتابة مرة واحدة والتشغيل في أي مكان "write once, run anywhere" مستعينة بما يسمى آلة جافا الوهمية Java Virtual Machine أو ما يعرف اختصارًا <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">JVM</a>، فأي نظام تشغيل مثبت عليه JVM يمكنه تشغيل تطبيقات جافا دون أي تغيير في الكود، وآلة جافا الوهمية أو الافتراضية هي عبارة عن برنامج موجود ضمن نظام التشغيل مهمته تفسير كود البايت لجافا Java bytecode كي يتمكن من العمل على أجهزة مختلفة وأنظمة مختلفة سواء ويندوز أو لينكس أو ماك أو أندرويد …إلخ فهي بمثلة المحرك الذي يحول كود البايت إلى لغة الآلة الخاص بكل نظام.
</p>

<p>
	لم تكن هذه الآلية في العمل متاحة في لغات البرمجة الأخرى والتي كانت تتطلب إعادة كتابة التعليمات البرمجية لكل نظام تشغيل على حدا. ولعل هذه الميزة هي أبرز ما ميز لغة جافا عن باقي لغات البرمجة، ولا تقتصر أهمية آلة جافا الوهمية على تمكين برنامج جافا من العمل في أي بيئة تشغيل، بل تساهم الآلة الوهمية كذلك في الحفاظ على ذاكرة برامج جافا وتحسينها فهي تتضمن ميزة تراقب البرنامج بشكل مستمر وتبحث عن أي بيانات مهملة وغير مستخدمة في الذاكرة وإزالتها ما يساهم في زيادة أداء البرامج وسرعتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="146709" href="https://academy.hsoub.com/uploads/monthly_2024_03/--jvm.png.ab9d02c54f30d6fa0aa267630df7240e.png" rel=""><img alt="ما هي jvm" class="ipsImage ipsImage_thumbnailed" data-fileid="146709" data-ratio="95.00" data-unique="jcbgb1d8x" style="width: 500px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2024_03/--jvm.thumb.png.0ab0418107e2ab695a99f75cac53e2a0.png"></a>
</p>

<h2 id="-1">
	مميزات لغة جافا
</h2>

<p>
	تتميز لغة جافا بالعديد من الجوانب الإيجابية ومن أبرزها:
</p>

<ul>
	<li>
		تعد واحدة من لغات البرمجة عالية المستوى <a href="https://academy.hsoub.com/programming/general/%d8%a3%d8%b3%d9%87%d9%84-%d9%84%d8%ba%d8%a7%d8%aa-%d8%a7%d9%84%d8%a8%d8%b1%d9%85%d8%ac%d8%a9/" rel="">وسهلة التعلم</a>.
	</li>
	<li>
		لغة عامة الأغراض ومتعددة الاستخدامات وتصلح لتطوير العديد من التطبيقات.
	</li>
	<li>
		لغة مشهورة وذائعة الصيت وتملك مجتمع دعم كبير ونشيط يضم الكثير من المطورين المحترفين.
	</li>
	<li>
		يمكن تشغيل أكواد جافا على أي نظام تشغيل بفضل آلة جافا الوهمية JVM.
	</li>
	<li>
		توفر ميزات تعزز أداء البرامج مثل ميزة تجميع للبيانات المهملة في الذاكرة ومسحها تلقائيًا.
	</li>
	<li>
		لغة برمجة آمنة وتوفر العديد من المميزات لحماية التطبيقات.
	</li>
	<li>
		تدعم البرمجة الموزعة أي يمكن بسهولة توزيع تطبيقات Java عبر أجهزة متعددة.
	</li>
</ul>

<h2 id="-2">
	سلبيات لغة جافا
</h2>

<ul>
	<li>
		بالرغم من أن لغة جافا تعد واحدة من لغات البرمجة سهلة التعلم لكنها أصعب من لغات أخرى أحدث مثل بايثون وروبي والتي تناسب المبتدئين أكثر.
	</li>
	<li>
		تعد لغة جافا أبطأ من بعض لغات البرمجة الأخرى مثل <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-c-%D9%88%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AE%D8%B7%D9%88%D8%A7%D8%AA-%D8%AA%D8%B9%D9%84%D9%85%D9%87%D8%A7-r2268/" rel="">C</a> و<a href="https://academy.hsoub.com/programming/cpp/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-c-r802/" rel="">C++‎</a>.
	</li>
	<li>
		لا توفر الكثير من الأدوات القوية فيما يتعلق بإنشاء واجهات المستخدم الرسومية GUI مقارنة بلغات أخرى مثل <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a> أو <a href="https://academy.hsoub.com/programming/c-sharp/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-c-r597/" rel="">C#‎</a>.
	</li>
	<li>
		استخدام لغة جافا مجاني للاستخدامات الشخصية والتطويرية، لكن شركة أوراكل تفرض رسومًا مالية للاستخدام التجاري لإصدارات جافا من الإصدار 11 والإصدارات الأحدث.
	</li>
</ul>

<h2 id="-3">
	استخدامات جافا
</h2>

<p>
	تتميز لغة جافا Java بكونها لغة عامة الأغراض وهي تصلح للاستخدام في مجموعة واسعة من التطبيقات ومن أبرز استخدامات جافا نذكر:
</p>

<ul>
	<li>
		تطوير تطبيقات الجوال (تطبيقات أندرويد Android)
	</li>
	<li>
		تطوير الويب
	</li>
	<li>
		تطوير تطبيقات سطح المكتب
	</li>
	<li>
		برمجة ألعاب الفيديو والألعاب الإلكترونية
	</li>
	<li>
		برمجة تطبيقات الذكاء الاصطناعي وإنترنت الأشياء
	</li>
</ul>

<p>
	لنناقش كل استخدام من هذه الاستخدامات وأهمية استخدام لغة البرمجة جافا فيه.
</p>

<h3 id="android">
	تطوير تطبيقات الجوال (تطبيقات أندرويد Android)
</h3>

<p>
	كانت لغة جافا Java اللغة الافتراضية المعتمدة من قبل جوجل من أجل <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%86%D8%AF%D8%B1%D9%88%D9%8A%D8%AF-r1802/" rel="">برمجة تطبيقات الأندرويد</a> وهي توفر العديد من المكتبات والأدوات المساعدة في تطوير تطبيقات جوال أصيلة Native سريعة وعالية الأداء وقادرة على الوصول بسهولة إلى كافة موارد ومميزات الجوال.
</p>

<p>
	وبالرغم من أن جوجل طورت فيما بعد <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D9%83%D9%88%D8%AA%D9%84%D9%86-kotlin-r2251/" rel="">لغة كوتلن Kotlin</a> واعتبرتها اللغة البرمجية الجديدة المعتمدة لتطوير تطبيقات أندرويد عام 2019 إلا أن لغة جافا لا تزال مستخدمة بشكل كبير في <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%AC%D9%88%D8%A7%D9%84-r1801/" rel="">برمجة تطبيقات الجوال</a>.
</p>

<h3 id="-4">
	تطوير الويب
</h3>

<p>
	تصلح لغة جافا أيضًا للاستخدام في مجال <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تطوير الويب</a>، فهي توفر مجموعة كبيرة من المكتبات و<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">أطر العمل</a> وواجهات برمجة التطبيقات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">APIs</a> التي تسهل عليك تطوير تطبيقات الويب نذكر من بينها: Spring وMicroNaut و Google Web Toolkit أو اختصارًا GWT، كما أنها لغة برمجة آمنة وتوفر العديد من الميزات المدمجة التي يحتاجها مطورو الويب لتطوير تطبيقات آمنة وموثوقة مثل ميزة التحكم في الوصول Access control والمصادقة Authentication.
</p>

<h3 id="-5">
	تطوير تطبيقات سطح المكتب
</h3>

<p>
	تساعد لغة جافا على <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%B3%D8%B7%D8%AD-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8/" rel="">برمجة تطبيقات سطح مكتب</a> متوافقة مع كافة أنظمة التشغيل وذات واجهات مستخدم أنيقة وقادرة على تخزين ومعالجة البيانات بكفاءة، وتوفر العديد من الأدوات المساعدة لتطوير تطبيقات سطح المكتب وأشهرها Java Swing و JavaFX وهي عبارة عن مجموعة أدوات توفر لك ما تحتاجه لبناء واجهات المستخدم الرسومية GUI لتطبيقات سطح المكتب.
</p>

<h3 id="-6">
	برمجة الألعاب الإلكترونية
</h3>

<p>
	تعد لغة جافا Java واحدة من أشهر <a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">لغات برمجة الألعاب الإلكترونية </a> وبشكل خاص ألعاب الجوال فهي تدعم ميزة تعدد الخيوط <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">threads</a> والمعالجة على التوازي، وتوفر <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-game-engines/" rel="">محركات ألعاب</a> ومكتبات وأطر عمل مساعدة في مجال تطوير الألعاب ثنائية وثلاثية الأبعاد متعددة المنصات مثل jMonkeyEngine و libGDX وLWJGL.
</p>

<p>
	ومن أشهر الألعاب المطورة باستخدام لغة البرمجة جافا نذكر:
</p>

<ul>
	<li>
		لعبة ماين كرافت Minecraft
	</li>
	<li>
		لعبة Asphalt 6
	</li>
	<li>
		لعبة Star Wars Galaxies
	</li>
</ul>

<h3 id="-7">
	برمجة تطبيقات الذكاء الاصطناعي وإنترنت الأشياء
</h3>

<p>
	تناسب لغة البرمجة جافا Java تطوير تطبيقات <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A/?utm_content=buffer0a33a&amp;utm_medium=social&amp;utm_source=linkedin.com&amp;utm_campaign=buffer" rel="">الذكاء الاصطناعي وتعلم الآلة</a> وتطوير تطبيقات مضمنة لإنترنت الأشياء <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%88-%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A7%D9%84%D8%A3%D8%B4%D9%8A%D8%A7%D8%A1-iot-r1783/" rel="">IoT</a> فهي توفر العديد من مكتبات تعلم الآلة، كما توفر واجهات برمجة التطبيقات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">APIs</a> مفيدة في هذا المجال مثل مكتبة TensorFlow و Scikit learn و JML و Weka والتي يمكن الاعتماد عليها لتطوير تطبيقات ذكاء اصطناعي متوافقة مع مختلف المنصات، كما أنها لغة برمجة قوية قادرة على التعامل بكفاءة مع البيانات الضخمة <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%B6%D8%AE%D9%85%D8%A9-big-data-r1579/" rel="">Big Data</a> وتحليلها بسرعة وفعالية.
</p>

<h2 id="-8">
	ما الفرق بين جافا و جافا سكريبت
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="146708" href="https://academy.hsoub.com/uploads/monthly_2024_03/----.png.16328866cfa615aba150089b972aa784.png" rel=""><img alt="الفرق بين جافا وجافا سكريبت" class="ipsImage ipsImage_thumbnailed" data-fileid="146708" data-ratio="62.60" data-unique="n6ih915yo" style="width: 500px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2024_03/----.thumb.png.93c2036598d7805acef0fefd0c37b2b9.png"> </a>
</p>

<p>
	بالرغم من تشابه اسمي اللغتين، إلا أن لغة جافا ولغة جافا سكريبت هما لغتا برمجة مختلفتان تمامًا، ولكل منهم صياغة وطريقة مختلفة في كتابة التعليمات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r2244/" rel="">والأكواد البرمجية</a> لكنهما تشتركان في بعض الجوانب وتختلفان في جوانب أخرى.
</p>

<p>
	تتشابه هاتان اللغتان بكونهما من لغات البرمجة عالية المستوى والشائعة بين معشر المطورين، لكن لغة البرمجة جافا هي لغة برمجة <a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/" rel="">مصرفة compiled</a> عامة الأغراض تستخدم لإنشاء مختلف أنواع التطبيقات التي تعمل ضمن آلة جافا الافتراضية JVM، أو ضمن متصفح يدعم جافا من خلال ما يعرف باسم بريمجات جافا Java applet وتعتمد لغة جافا بشكل أساسي على نموذج <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-r1375/" rel="">البرمجة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a> .
</p>

<p>
	أما <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D9%85%D9%86-%D8%A7%D9%84%D8%B5%D9%81%D8%B1-%D8%AD%D8%AA%D9%89-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D8%B1%D8%A7%D9%81-r2046/" rel="">لغة جافا سكريبت</a> فهي لغة برمجة نصية مفسرة interpreted طورتها شركة Netscape Communications عام 1995 وكان الغرض الأساسي منها هو جعل <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8%D8%9F-r2094/" rel="">صفحات الويب</a> تفاعلية وديناميكية تعمل ضمن المتصفح أو من طرف العميل كما أنها أصبحت فيما بعد قادرة على العمل خارج المتصفح والعمل من جانب الخادم بفضل بيئة التشغيل نود جي إس Node.js وتدعم لغة جافا سكريبت كل من نموذج <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A5%D8%AC%D8%B1%D8%A7%D8%A6%D9%8A%D8%A9/" rel="">البرمجة الإجرائية</a> ونموذج <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel="">البرمجة كائنية التوجه</a> بنفس الوقت.
</p>

<h2>
	ما الفرق بين جافا وكوتلن
</h2>

<p>
	لغة كوتلن Kotlin هي لغة برمجة منبثقة عن لغة جافا Java وهي تعد كذلك من لغات البرمجة القوية والتي تتشابه مع لغة جافا في كونها <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel="">لغة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a>  وتعمل ضمن آلة جافا الافتراضية JVM وتتوافق مع العديد من المنصات والأنظمة، كما أنها لغة عامة الأغراض وتستخدم لتطوير مختلف التطبيقات مثل تطبيقات الجوال والويب.
</p>

<p>
	ورغم تشابه اللغتين في عدة جوانب إلا أن لغة كوتلن Kotlin أحدث من لغة جافا، وتضيف عدة تحسينات عليها، إذ توفر كوتلن نظامًا صارمًا للتحقق من الأنواع يسهل على المطورين كشف الأخطاء وصيانة الكود البرمجي، وتوفر ميزات لتعزيز أمان التطبيقات مثل أمان القيم الفارغة Null Safety التي تمنع المبرمج من تعيين قيمة فارغة لمتغير ما، فضلًا عن كون شيفراتها البرمجية أكثر اختصارًا وأسهل مقروئيةً، وتستهلك موارد وذاكرة أقل من نظيرتها المكتوبة بلغة جافا Java، وهي كذلك تمكنك من كتابة توابع برمجية جديدة لتوسيع الأصناف البرمجية classes دون الحاجة إلى تعديل الأصناف نفسها.
</p>

<h2 id="-9">
	خطوات تعلم جافا
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="146710" href="https://academy.hsoub.com/uploads/monthly_2024_03/---.png.e146584ea286be648648f7f48959dd32.png" rel=""><img alt="ما-هي-لغة-جافا.png" class="ipsImage ipsImage_thumbnailed" data-fileid="146710" data-ratio="62.60" data-unique="561v95ifm" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_03/---.thumb.png.e2b57295692caf914cc56971c42756ae.png"></a>
</p>

<p>
	إذا كنت ترغب في تعلم جافا Java ولكنك مشتت ولا تعرف من أين تبدأ، فإليك قائمة بأبرز الخطوات التي تساعدك على تعلم لغة جافا من الصفر حتى الاحتراف:
</p>

<ul>
	<li>
		قبل البدء بخطوات تعلم لغة جافا أسس نفسك في <a href="https://academy.hsoub.com/programming/advanced/%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA/" rel="">الخوارزميات</a> <a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%87%D9%85%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%81%D9%83%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D9%86%D8%B7%D9%82%D9%8A-%D9%81%D9%8A-%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r2095/" rel="">والتفكير المنطقي</a> فهي خطوة أساسية لتعلم أي لغة برمجة وفهمها بصورة أسرع.
	</li>
	<li>
		حدد هدفك من تعلم جافا ونوع التطبيقات الذي تهدف لتطويرها باستخدامها، فهذا يساعدك على التركيز على دراسة المواضيع التي تهمك أكثر من غيرها.
	</li>
	<li>
		تعلم كيفية تثبيت لغة جافا على جهازك وكيفية استخدام أحدد محررات الأكواد أو بيئات التطوير المتكاملة IDE الخاصة بها كي تتمكن من كتابة وتنفيذ أكواد جافا وجرب <a href="https://academy.hsoub.com/programming/java/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D8%A3%D9%88%D9%84%D9%8A-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1015/" rel="">كتابة أول برنامج لك في جافا</a>.
	</li>
	<li>
		تعلم <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">أساسيات البرمجة</a> بلغة جافا مثل أسلوب كتابة شيفرات جافا ومفاهيم المتغيرات وأنواع البيانات الأساسية وهياكل البيانات في جافا واستخداماتها وكيفيه كتابة تعليمات التحكم كالشروط وحلقات التكرار واستخدام الدوال البرمجية.
	</li>
	<li>
		طبق ما تتعلمه من أساسيات على برامج بسيطة مثل برامج حل المعادلات أو ترتيب مجموعة من البيانات بطرق مختلفة، وتدرب على اكتشاف ومعالجة الأخطاء البرمجية فهذه مهارة أساسية لأي مبرمج.
	</li>
	<li>
		بعد أن تتقن الأساسيات انتقل لتعلم مفاهيم متقدمة في جافا وأهمها مبادئ البرمجة كائنية التوجه <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel=""><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a> ومفاهيم الأصناف والكائنات وتعدد الخيوط<br>
		Java Multi-threading، والحزم والواجهات، وآلية التعامل مع استثناءات جافا <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%A7%D8%AA-exceptions-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1309/" rel="">Exceptions</a>، وعمليات الإدخال والإخراج، وغيرها من التقنيات التي تساعدك على <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AB%D8%A7%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%8A-%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%AD%D9%88%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AA%D9%82%D9%86%D9%8A%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1539/" rel="">بناء تطبيقات متكاملة بلغة جافا</a>.
	</li>
	<li>
		تعلم أهم المكتبات وأطر العمل المفيدة لتطوير التطبيقات التي تهتم بها وفق الهدف أو المجال الذي حددته في الخطوة الأولى، وابدأ بتطوير تطبيقات متكاملة تعزز خبرتك في هذا المجال وطور عدة مشاريع تعزز خبرتك في هذا المجال.
	</li>
	<li>
		ابحث عن فرصة عمل مناسبة للعمل كمطور جافا وبناء مشاريع فعلية لعملاء حقيقين، فالعمل الفعلي هو ما يعزز خبرتك الحقيقة ويساعدك على معرفة متطلبات سوق العمل بشكل أفضل.
	</li>
	<li>
		لا تتوقف عن التعلم أبدًا، فالبرمجة مجال متجدد ومتطور بشكل مستمر وعليك التعلم بصورة مستمرة لتتمكن من الحفاظ على الصدارة ولا تتقادم معلومات.
	</li>
</ul>

<p>
	ستجد الكثير من مصادر التعلم المنوعة عبر الانترنت التي تساعدك على تعلم برمجة جافا بصورة ذاتية من <a href="https://academy.hsoub.com/programming/java/" rel="">دروس ومقالات لتعلم جافا</a> ومقاطع فيديو وكتب ودورات تدريبية وغيرها، لكن من المهم أن تنظم وقتك في التعلم وتختار مصادر تعلم موثوقة تناسب أسلوبك في التعلم ولا تشتت نفسك بكثرة المصادر.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="499" id="ips_uid_6283_6" src="https://academy.hsoub.com/applications/core/interface/index.html" title="كيف تنظم وقتك عند تعلم البرمجة" width="887" data-embed-src="https://www.youtube.com/embed/CGNNUIvjmpw"></iframe>
</p>

<p>
	كما يمكنك الاطلاع على <a href="https://academy.hsoub.com/programming/java/" rel="">دورس ومقالات لغة جافا</a> التي تنشرها أكاديمية حسوب بصورة دورية، وإذا واجهت أي تساؤل حول لغة جافا يمكن طرحه في قسم <a href="https://academy.hsoub.com/questions/c3-programming/" rel="">أسئلة البرمجة</a> في الأكاديمية أو في <a href="https://io.hsoub.com/" rel="external">مجتمع حسوب IO</a> ليجيبك عليك نخبة من المبرمجين والمطورين المحترفين.
</p>

<h2 id="-10">
	الخلاصة
</h2>

<p>
	تعرفنا اليوم على لغة البرمجة جافا التي تعد واحدة من لغات البرمجة المشهورة ومتعددة الاستخدامات والتي تصلح لإنشاء مجموعة متنوعة من التطبيقات بدءًا من تطبيقات الهاتف الجوال والألعاب البسيطة وحتى البرامج المتطورة كبرامج المحاسبة وتطبيقات الذكاء الاصطناعي وتحليل البيانات الضخمة، ومهما كان التخصص أو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AC%D8%A7%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">المجال البرمجي</a> الذي تهتم به فإن قرار تعلم الجافا سيكون ملائمًا للعمل بهذا التخصص.
</p>

<h2 id="-11">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">الدليل السريع للغة البرمجة Java</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%A7-%D9%87%D9%8A%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r1515/" rel="">تعرف على ماهية جافا Java</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/" rel="">مدخل إلى أساسيات البرمجة بلغة Java: ما هي البرمجة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1308/" rel="">كيفية كتابة برامج صحيحة باستخدام لغة جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/kotlin/kotlin-%D9%87%D9%88-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%AC%D8%AF%D9%8A%D8%AF-r641/" rel="">Kotlin هو جافا الجديد</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2275</guid><pubDate>Wed, 20 Mar 2024 12:00:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x645;&#x627; &#x647;&#x64A;&#x629; &#x62C;&#x627;&#x641;&#x627; Java</title><link>https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%A7-%D9%87%D9%8A%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r1515/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_03/6241b6065de18_---Java.png.475ab0256b2d412f65289da64f210c12.png" /></p>
<p>
	تُعَد جافا منصةً برمجيةً ولغة برمجة حاسوب، وواحدةً من أكثر التقنيات انتشارًا في العالم الحديث.
</p>

<p>
	تُستخدم كلمة جافا عادةً للإشارة إلى شيئين، هما منصة جافا Java platform، وهي مجموعة من الأدوات التي تتيح تطوير التطبيقات متعددة المنصات بسهولة، و<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">لغة برمجة جافا</a>، وهي لغة برمجة عامة الاستخدام، تُستخدم غالبًا لتطوير البرامج لهذه المنصة.
</p>

<p>
	تتميز جافا عن العديد من التقنيات الأخرى بأنها مصممة بحيث يمكن تشغيل التعليمات البرمجية المكتوبة بلغة جافا على أي نظام يمكنه تشغيل <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">آلة جافا الافتراضية Java virtual machine</a>، واختصارًا JVM، وقد رُفع شعار "اكتبه مرةً واحدةً وشغله في أي مكان" للترويج لقدرات جافا متعددة المنصات، حيث يمكن إنشاء بيئات جافا على جميع أنواع الأجهزة، الكبيرة منها والصغيرة، وبالتالي يتمتع مطور جافا بالمرونة، لأن الشيفرة البرمجية تُعامل على أنها غير مرتبطة بالنظام الذي تُشغل عليه.
</p>

<p>
	لغة جافا لغة كائنية التوجه، وتشبه من الناحية التركيبية <a href="https://academy.hsoub.com/programming/cpp/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-c-r802/" rel="">لغة ++C</a>، وستلاحظ أن برامج جافا مبنية دائمًا بتصميم كائني التوجه على عكس بعض اللغات الأخرى التي سبقتها، والتي طبقت مفهوم الأصناف classes ولكنها لم تتطلب استخدامها.
</p>

<p>
	تقترن لغة جافا مع آلة جافا الافتراضية JVM -التي تشغّل التعليمات البرمجية المكتوبة بلغة جافا- اقترانًا وثيقًا، على الرغم من أنهما منفصلان، كما يمكن تشغيل الشيفرة البرمجية المكتوبة بلغات أخرى مصممة خصيصًا لآلة جافا الافتراضية، مثل Groovy و Scala عليها.
</p>

<p>
	احرص على عدم الخلط بين جافا و<a href="https://wiki.hsoub.com/JavaScript" rel="external">جافاسكربت JavaScript</a>، فبالرغم من اشتراكهما في جزء من الاسم، إلا أنهما يختلفان اختلافًا كبيرًا، وتُستخدمان في العديد من البيئات، إلا أن <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">جافاسكربت</a> تستخدم لتعزيز التفاعل داخل متصفح الويب.
</p>

<h2>
	ما هي استخدامات جافا؟
</h2>

<p>
	يمكن العثور على جافا في جميع الأماكن، ربما في جيبك أو على معصمك، حيث إن نظام أندرويد Android، وهو نظام تشغيل مفتوح المصدر مشتق من <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">نظام التشغيل لينكس Linux</a>، ويُشغل ملايين الأجهزة المحمولة حول العالم، يستخدمها مع مكتباته أساسًا لتطبيقات الأجهزة المحمولة المبنية لمنصته، كما يمكن استخدام جافا في <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%B3%D8%B7%D8%AD-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8/" rel="">تطبيقات سطح المكتب</a>، وتوجد العديد من أنواع التطبيقات التي تعمل على جافا، بدءًا من الألعاب ذات الشعبية الكبيرة -مثل Minecraft- إلى بيئة التطوير المتكاملة Eclipse التي يستخدمها المطورون للعديد من اللغات.
</p>

<p>
	كما تعمل جافا على تشغيل عدد من التطبيقات المصممة خصيصًا للويب، ومع التحسينات المطبقة على JavaScript و <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a>، لم تعد بريمجات جافا Java applet المعيار الفعلي لتطبيقات الويب التفاعلية، إلا أن الكثيرين لا زالوا يعتمدون على جافا لتوفير تجربة تفاعلية داخل المتصفح، وعلى الرغم من أن جافا ليست شائعة الاستخدام في <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/#%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA%20%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9%20Front-End" rel="">تطوير الواجهة الأمامية front-end</a> لتطبيقات الويب هذه الأيام، إلا أنها شائعة الاستخدام خلف الكواليس في الكثير من المواقع وتطبيقات الويب، لأن لها نظامًا بيئيًا غنيًا من الأدوات لتشغيل وتوصيل التطبيقات القابلة للتوسع على نطاق كبير، والتي تحافظ على تشغيل بعض أكبر مواقع الويب والعمليات التجارية، وذلك من خلال الإمكانيات التي توفرها منصة جافا التجارية Java EE و خوادم جافا مفتوحة المصدر، مثل <a href="https://www.wildfly.org/" rel="external nofollow">WildFly</a> و<a href="https://tomcat.apache.org/" rel="external nofollow">Apache Tomcat</a> .
</p>

<h2>
	هل جافا مفتوحة المصدر؟
</h2>

<p>
	يعد موضوع ترخيص جافا قصةً طويلةً ومعقدةً، ولكن حاليًا توجد معظم المكونات الرئيسية لجافا بموجب تراخيص مفتوحة المصدر، وتتوافر عادةً بدائل مفتوحة للمكونات غير الداخلة في الرخصة المفتوحة.
</p>

<p>
	وضعت شركة Sun (وهي المطور الأصلي لجافا) الكثير من مكونات جافا تحت رخصة GNU العامة في عام 2006، وسدت عدة مشاريع، مثل IcedTea، الفجوات الخاصة بأجزاء عُدة تطوير جافا Java Development Kit، والتي تختصر إلى JDK، غير الداخلة في الرخصة المفتوحة، مما يعني أنه من الممكن اليوم تشغيل تطبيقات جافا دون استخدام أي شيفرة تحتاج إلى رخصة.
</p>

<p>
	وتجدر الإشارة إلى توافر نسخة مفتوحة المصدر من عُدة تطوير جافا وهي <a href="https://ar.wikipedia.org/wiki/%D8%B9%D8%AF%D8%A9_%D8%AA%D8%B7%D9%88%D9%8A%D8%B1_%D8%AC%D8%A7%D9%81%D8%A7_%D8%A7%D9%84%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9" rel="external nofollow">openJDK</a>، التي صدرت تحت رخصة GPL وهي المرجع الرسمي لجافا SE بدءًا من الإصدار 7.
</p>

<h2>
	كيفية تشغيل تطبيق جافا
</h2>

<p>
	اتجهت اللغة نحو مفهوم الوحدات modules منذ الإصدار 9، حيث تحتوي الوحدة على مجموعة من الحزم، وتجمّع العديد من تطبيقات جافا الحديثة حزمة وحدة جافا صغيرة مثل جزء من البرنامج نفسه، مما يعني إمكانية تشغيل البرنامج دون الحاجة إلى تنزيل جافا.
</p>

<p>
	لا يشمل هذا جميع التطبيقات بالتأكيد، ولا بد من تثبيت بيئة تشغيل جافا Java runtime environment، والتي تختصر إلى JRE، لتشغيل العديد من تطبيقات جافا، ولا توجد بيئة تشغيل واحدة فقط لأن جافا مفتوحة المصدر، ولكن يمكن اختيار بيئة التشغيل التي تناسبك، فمثلًا توفر <a href="https://academy.hsoub.com/devops/linux/%d8%a7%d9%84%d8%af%d9%84%d9%8a%d9%84-%d8%a7%d9%84%d9%86%d9%87%d8%a7%d8%a6%d9%8a-%d9%84%d8%a7%d8%ae%d8%aa%d9%8a%d8%a7%d8%b1-%d8%aa%d9%88%d8%b2%d9%8a%d8%b9%d8%a9-%d9%84%d9%8a%d9%86%d9%83%d8%b3-r48/" rel="">توزيعات لينكس Linux</a> بيئة تشغيل جافا OpenJRE أو IcedTea، بينما يمكن لمستخدمي ويندوز تنزيل بيئة تشغيل JRE <a href="https://developers.redhat.com/products/openjdk/download" rel="external nofollow">من بوابة مطوري Red Hat</a>، أما مستخدمو ماك Mac OS (و Windows و Linux) فيمكنهم تنزيل بيئة تشغيل جافا JRE من <a href="http://jdk.java.net/13/" rel="external nofollow">OpenJDK.java.net</a> أو مجتمع <a href="https://www.azul.com/downloads/" rel="external nofollow">Zulu</a> في Azul.
</p>

<p>
	تُصمم بيئة تشغيل جافا JRE خصيصًا لنظام تشغيل معين يمكن تثبيتها عليه، حينها نستطيع تشغيل أي تطبيق جافا على الجهاز، و<a href="https://academy.hsoub.com/devops/linux/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-apt-r418/" rel="">بتثبيت جافا</a> فإنك تزود نظامك بطبقة -تقنيًا هي آلة جافا الافتراضية- يمكن تشغيل أي تطبيق جافا عليها.
</p>

<h2>
	كيفية البرمجة في جافا
</h2>

<p>
	جافا هي لغة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr>، وصارمة في تحديد النوع strictly typed، وتستخدَم في جميع الصناعات تقريبًا، من المالية إلى النشر إلى تكنولوجيا المعلومات، وتمتلك جمهورًا كبيرًا لأنها متعددة المنصات، وتبدأ مناهج البرمجة الأساسية في العديد من الجامعات بتعليم لغة جافا، لأنها تفرض أفضل ممارسات كتابة الشيفرة، مثل تحديد النطاق وتحديد نوع المتغيرات، والتي تسمح لغات أخرى بتجاهلها، مثل <a href="https://wiki.hsoub.com/Python" rel="external">لغة Python</a> و<a href="https://academy.hsoub.com/programming/go/%d8%aa%d8%b9%d8%b1%d9%81-%d8%b9%d9%84%d9%89-%d9%84%d8%ba%d8%a9-%d8%a7%d9%84%d8%a8%d8%b1%d9%85%d8%ac%d8%a9-go-r222/" rel="">لغة GO</a>، وتعد جافا شائعةً وقديمةً إلى درجة احتوائها على مكتبات لأي مهمة تقريبًا، لذلك يمكن للمبرمج المبتدئ الوصول إلى مجموعة غنية من الدوال الجاهزة للاستخدام.
</p>

<p>
	لاستخدام جافا يجب تثبيت عدة تطوير جافا JDK، ويمكنك الانتقاء من عدة خيارات، بما في ذلك <a href="http://openjdk.java.net/" rel="external nofollow">OpenJDK</a> و <a href="https://www.azul.com/downloads/" rel="external nofollow">Zulu</a>، وهي JDK تحتوي على JRE، بحيث يمكنك تشغيل التطبيقات التي تطورها.
</p>

<p>
	توجد العديد من الطرق لبدء التعرف على جافا، فإذا كنت تفضل مقدمةً ممتعةً، فيمكنك تجربة <a href="https://www.greenfoot.org/door" rel="external nofollow">Greenfoot</a>، وهي بيئة تطوير تفاعلية interactive development environment، وتختصر إلى IDE، مصممة لتعليم جافا، وتساعد المبرمج الجديد على إنشاء ألعاب وتطبيقات ممتعة بواجهة رسومية سهلة.
</p>

<p>
	فإذا أردت خطوةً أكثر تقدمًا فاستخدم <a href="https://bluej.org/" rel="external nofollow">BlueJ</a>، وهو بيئة تطوير تفاعلية لجافا صممته كلية King's في لندن، وهو يسلط الضوء على عناصر مهمة من الشيفرة، مثل الأصناف والكائنات.
</p>

<p>
	تعد بيئة التطوير <a href="https://www.eclipse.org/" rel="external nofollow">Eclipse</a> أحد أكثر بيئات برمجة جافا شهرةً وقدرةً، وتساعد في إدارة المكتبات، وحل الأخطاء والتعارضات في التعليمات البرمجية، كما تساعد في التنقيح debug وإنشاء الحزم وغير ذلك، ويوفر <a href="https://www.eclipse.org/che/" rel="external nofollow">Eclipse Che</a> لفرق التطوير مساحة عمل مشتركةً قائمةً على السحابة، مما يضمن استخدام كل شخص في المشروع لنفس البيئة.
</p>

<h2>
	مستقبل جافا
</h2>

<p>
	طوَّرت شركة Sun Microsystems لغة جافا بدايةً ودعمتها فترةً من الزمن، ثم انتقلت جافا إلى جناح شركة أوراكل Oracle التي تدعمها هذه الأيام، ولا زالت هنالك نسخة مفتوحة المصدر من جافا هي openJDK يقف خلفها مجتمع عالمي يدعمها ويقف خلف نموها وتطويرها، ورأينا تغير استخدامات جافا بمرور الأيام، لكنها ما زالت ترفع شعارها الشهير "اكتبه مرةً واحدةً وشغله في أي مكان" بقوة، متحديةً أي لغة برمجة أن تضاهيها وتنافسها فيه.
</p>

<p>
	لمعرفة المزيد يمكنك العودة إلى <a href="https://academy.hsoub.com/tags/%D9%85%D8%AF%D8%AE%D9%84%20%D8%A5%D9%84%D9%89%20%D8%AC%D8%A7%D9%81%D8%A7/" rel="">مدخل إلى جافا</a>
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://opensource.com/resources/java" rel="external nofollow">What is Java?‎</a> من موقع opensource.com.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/" rel="">مدخل إلى أساسيات البرمجة بلغة Java: ما هي البرمجة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-programming-environment-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1020/" rel="">بيئات البرمجة (programming environment) في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">مقدمة إلى برمجة واجهات المستخدم الرسومية (GUI) في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1515</guid><pubDate>Sat, 23 Apr 2022 15:07:00 +0000</pubDate></item><item><title>&#x645;&#x62B;&#x627;&#x644; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x64A; &#x645;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x62D;&#x648;&#x644; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62A;&#x642;&#x646;&#x64A;&#x627;&#x62A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D8%AB%D8%A7%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%8A-%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%AD%D9%88%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AA%D9%82%D9%86%D9%8A%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1539/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/6260f4d109d2f_-.png.b6680d7a850c04733361e14fbc4bd73e.png" /></p>

<p>
	سنتناول خلال هذا المقال برنامجًا معقدًا بعض الشئ عما رأيناه مسبقًا، فقد كانت غالبية الأمثلة التي تعرَّضنا إليها مجرد أمثلة بسيطة هدفها توضيح تقنية برمجية أو اثنتين على الأكثر؛ أما الآن، فقد ان الوقت لتوظيف كل تلك الأفكار والتقنيات معًا ضمن برنامجٍ واحدٍ حقيقي. سنناقش أولًا البرنامج وتصميمه بصورةٍ مُبسطة، وبينما نفعل ذلك، سنتحدث سريعًا عن بعض خاصيات جافا التي لم نتمكَّن من الحديث عنها أثناء المقالات السابقة؛ إذ تتضمَّن تلك الخاصيات أمورًا يُمكِن تطبيقها على جميع البرامج وليس فقط على برامج <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">واجهات المُستخدم الرسومية GUI</a>.
</p>

<p>
	سنتحدث تحديدًا عن برنامج عرض مجموعة ماندلبرو Mandelbrot، والذي يَسمَح للمُستخدِم باكتشاف تلك المجموعة الشهيرة، إذ سنبدأ أولًا بشرح ما يعنيه ذلك. تتوفَّر <a href="http://math.hws.edu/eck/js/mandelbrot/java/MB-java.html" rel="external nofollow">نسخةٌ أقوى من هذا البرنامج</a>؛ و<a href="http://math.hws.edu/eck/js/mandelbrot/MB.html" rel="external nofollow">نسخةٌ أخرى</a> منه مكتوبةٌ <a href="https://wiki.hsoub.com/JavaScript" rel="external">بلغة JavaScript</a>، وقابلةٌ للتشغيل بمتصفحات الإنترنت.
</p>

<h2>
	مجموعة ماندلبرو Mandelbrot
</h2>

<p>
	مجموعة ماندلبرو هي مجموعةٌ من النقاط الواقعة على سطح مستوى xy، التي تُحسَب مواضعها بواسطة عمليةٍ حسابية؛ وكل ما تحتاج إلى معرفته لكي تتمكَّن من تشغيل البرنامج هو أن تَعرِف إمكانية استخدام هذه المجموعة لصناعة مجموعةٍ من الصور الرائعة. سننتقل الآن إلى التفاصيل الحسابية: لنفترض لدينا نقطة (a,b)، وكان الإحداثي الأفقي والرأسي لتلك النقطة مكونين من أعدادٍ حقيقية، عندها يُمكِننا إذًا تطبيق العمليات التالية عليها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_8" style="">
<span class="typ">Let</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> a
</span><span class="typ">Let</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> b
</span><span class="typ">Repeat</span><span class="pun">:</span><span class="pln">
   </span><span class="typ">Let</span><span class="pln"> newX </span><span class="pun">=</span><span class="pln"> x</span><span class="pun">*</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> y</span><span class="pun">*</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> a
   </span><span class="typ">Let</span><span class="pln"> newY </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">*</span><span class="pln">x</span><span class="pun">*</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> b
   </span><span class="typ">Let</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> newX
   </span><span class="typ">Let</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> newY</span></pre>

<p>
	تتغير إحداثيات النقطة (x,y) أثناء تنفيذ حلقة التكرار loop بالأعلى، ويَنقلنا ذلك إلى السؤال التالي: هل تزداد قيم إحداثيات النقطة (x,y) دون أي قيد أم أنها تقتصر على منطقة نهائية ضمن المستوى؟ إذا كانت (x,y) تذهب إلى اللانهاية (أي تزداد بدون قيد)، فإن نقطة البداية (a,b) لا تنتمي إلى مجموعة ماندلبرو؛ أما إذا كانت النقطة (x,y) مقتصرةً على منطقة نهائية، فإن نقطة البداية (a,b) تنتمي إلى المجموعة.
</p>

<p>
	من المعروف أنه لو أصبح 'x2 + y2' أكبر من '4' ضمن أي لحظة، فإن النقطة (x,y) تذهب إلى اللانهاية، وبالتالي لو أصبح 'x2 + y2' أكبر من '4' بالحلقة المُعرَّفة بالأعلى، فيُمكِننا أن نُنهِي الحلقة، ونستنتج أن النقطة (a,b) ليست ضمن مجموعة ماندلبرو بلا شك. في المقابل، بالنسبة لنقطة (a,b) تنتمي إلى تلك المجموعة، فإن الحلقة لن تنتهي أبدًا. إذا شغَّلنا ذلك على حاسوب، فإننا بالطبع لا نريد أن نحصل على حلقة لا نهائية تَعمَل إلى الأبد، ولذلك سنضع حدًا أقصى على عدد مرات تنفيذ الحلقة، وسيُمثِّل <code>maxIterations</code> ذلك الحد. ألقِ نظرةً إلى ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_10" style="">
<span class="pln">x </span><span class="pun">=</span><span class="pln"> a</span><span class="pun">;</span><span class="pln">
y </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">;</span><span class="pln">
count </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> x</span><span class="pun">*</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> y</span><span class="pun">*</span><span class="pln">y </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">4.1</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   count</span><span class="pun">++;</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">count </span><span class="pun">&gt;</span><span class="pln"> maxIterations</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">double</span><span class="pln"> newX </span><span class="pun">=</span><span class="pln"> x</span><span class="pun">*</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> y</span><span class="pun">*</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> a</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">double</span><span class="pln"> newY </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">*</span><span class="pln">x</span><span class="pun">*</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> b</span><span class="pun">;</span><span class="pln">
   x </span><span class="pun">=</span><span class="pln"> newY</span><span class="pun">;</span><span class="pln">
   y </span><span class="pun">=</span><span class="pln"> newY</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد انتهاء الحلقة، وإذا كانت قيمة <code>count</code> أقل من أو تُساوِي <code>maxIterations</code>؛ فعندها يُمكِننا أن نستنتج أن النقطة (a,b) لا تنتمي إلى مجموعة ماندلبرو؛ أما إذا كانت قيمة <code>count</code> أكبر من <code>maxIterations</code>، فقد تنتمي النقطة (a,b) إلى المجموعة أو لا؛ وفي العموم كلما كانت قيمة <code>maxIterations</code> أكبر، كلما زادت احتمالية انتماء النقطة (a,b) إلى المجموعة.
</p>

<p>
	سنُنشِئ صورةً باستخدام العملية الحسابية السابقة على النحو التالي: سنَستخدِم شبكةً مستطيلةً من البكسلات لتمثيل مستطيلٍ واقعٍ على سطح المستوى، بحيث يتوافق كل بكسل مع إحداثيات قيمها حقيقية (a,b)، إذ تُشير قيم الإحداثيات إلى مركز البكسل. سنُشغِّل حلقة التكرار بالأعلى لكل بكسل؛ فإذا تعدَّت قيمة <code>count</code> قيمة الحد الأقصى <code>maxIterations</code>، فسنُلّون البكسل باللون الأسود، مما يعني أن تلك النقطة قد تنتمي إلى مجموعة ماندلبرو؛ أما إذا لم يتعداه، فسيعتمد لون البكسل على قيمة <code>count</code> بعد انتهاء الحلقة، مما يَعنِي أننا سنَستخدِم ألوانًا مختلفةً للقيم المختلفة من <code>count</code>.
</p>

<p>
	كلما ازدادت قيمة <code>count</code>، كانت النقطة أقرب إلى مجموعة ماندلبرو؛ وبالتالي تُعطِي الألوان بعض المعلومات عن النقاط الواقعة خارج المجموعة وعن شكل المجموعة، ولكن من المهم أن تدرك أن تلك الألوان عشوائية وأن النقاط الملونة لا تنتمي إلى المجموعة.
</p>

<p>
	تَعرِض الصورة التالية لقطة شاشة من برنامج عرض مجموعة ماندلبرو، الذي يَستخدِم نفس تلك العملية الحسابية؛ إذ تُمثِّل المنطقة السوداء مجموعة ماندلبرو، باستثناء أن بعض النقاط السوداء قد لا تكون ضمن المجموعة فعليًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="96828" href="https://academy.hsoub.com/uploads/monthly_2022_04/001Mandelbrot.png.844bdb8da17ab99ee92047ddb151bcdd.png" rel=""><img alt="001Mandelbrot.png" class="ipsImage ipsImage_thumbnailed" data-fileid="96828" data-unique="6zm2ah2ri" src="https://academy.hsoub.com/uploads/monthly_2022_04/001Mandelbrot.png.844bdb8da17ab99ee92047ddb151bcdd.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	إذا شغَّلت البرنامج فبإمكانك تكبير الصورة حول أي منطقة صغيرة ضمن سطح المستوى، وذلك بنقر زر الفأرة على الصورة مع السحب؛ إذ يؤدي ذلك إلى رسم "صندوق تكبير" مستطيل الشكل حول جزء الصورة ذاك كما هو مُوضَّح بالأعلى. وعندما تُحرِّر زر الفأرة، سيزداد حجم جزء الصورة الموجود داخل صندوق التكبير لكي يملأ الشاشة بالكامل؛ أما إذا نقرت على أي نقطة ضمن الصورة، فسيزداد حجم الصورة أيضًا عند النقطة التي نقرت عليها بمعدل تكبير يساوي الضعف. انقر على "Shift" أو اِستخدِم زر الفأرة الأيمن لكي تُصغرّ حجم الصورة.
</p>

<p>
	تُعدّ النقاط الموجودة عند الحدود الفاصلة لمجموعة ماندلبرو هي النقاط الأكثر تشويقًا، وفي الحقيقة، يُعَد ذلك الفاصل معقدًا تعقيدًا لا نهائيًا؛ فإذا كبَّرت الصورة إلى حدٍ بعيد، فلن يمنعك البرنامج من فعل ذلك، ولكنك ستتعدّى إمكانيات نوع البيانات <code>double</code>، وستبدأ بكسلات الصورة في الظهور وستصبح الصورة بلا معنى.
</p>

<p>
	يُمكِنك استخدام قائمة "MaxIterations" لزيادة الحد الأقصى لعدد مرات تكرار الحلقة، وتذكّر أن البكسلات السوداء قد تقع أو لا ضمن المجموعة؛ فإذا أزدت قيمة <code>MaxIterations</code>، فقد تجد أن بعض المناطق السوداء قد أصبحت ملونةً هي الأخرى.
</p>

<p>
	تُحدِّد قائمة "Palette" مجموعة الألوان المُستخدَمة، ويؤدي استخدام لوحات ألوان مختلفة إلى إنتاج صورٍ مختلفة، ولكنها مُجرّد مجموعة ألوان عشوائية فقط. وتحدِّد قائمة "PaletteLength" عدد الألوان المختلفة المُستخدَمة؛ فإذا اِستخدَمت الإعدادات الافتراضية، فإن كل قيمة ممكنة للمتغير <code>count</code> يُقابلها قيمة لونٍ مختلفة.
</p>

<p>
	قد تحصل أحيانًا على صور أفضل بكثير باستخدام عددٍ مختلف من الألوان؛ فإذا كان عدد الألوان الموجودة بلوحة الألوان المُستخدَمة أقل من قيمة <code>maxIterations</code>، فسيتكرَّر نفس اللون لكي يشمل جميع القيم المحتملة للمتغير <code>count</code>؛ أما إذا كان عددها أكبر من قيمة <code>maxIterations</code>، فسيُستخدَم فقط جزءٌ من الألوان المُتاحة. لذلك، إذا كانت غالبية بكسلات الصورة من خارج المجموعة مُكوَّنة من درجات مختلفة من لون واحد تقريبًا، فقللّ عدد ألوان لوحة الألوان؛ إذ يؤدي ذلك إلى اختلاف الألوان بصورةٍ أسرع مع تغيُّر قيمة <code>count</code>؛ أما إذا وجدتها مُكوَّنةً من ألوان عشوائية تمامًا بدون أي انسيابية بين الألوان، زِد عدد ألوان لوحة الألون.
</p>

<p>
	يحتوي البرنامج على قائمة "File" يُمكِن استخدامها لحفظ الصورة مثل ملف صورة بامتداد PNG؛ ويُمكِنك أيضًا أن تحفظ ملف "param" لحفظ إعدادات البرنامج التي أنتجت الصورة الحالية؛ وتستطيع لاحقًا قراءة هذا الملف إلى البرنامج باستخدام الأمر "Open".
</p>

<p>
	يعود اسم مجموعة ماندلبرو إلى Benoit Mandelbrot، وهو أول من لاحظ ذلك التعقيد المذهل لتلك المجموعة، إذ من الرائع الحصول على هذا التعقيد والجمال من تلك الخوارزمية البسيطة.
</p>

<h2>
	تصميم تطبيق مجموعة ماندلبرو
</h2>

<p>
	نجد معظم الأصناف بلغة جافا مُعرَّفةً ضمن حزم packages؛ فعلى الرغم من أننا استخدمنا بعض الحزم القياسية، مثل <code>javafx.scene.control</code> و <code>java.io</code> بكثرة، إلا أن معظم الأمثلة التي تعرَّضنا إليها كانت تَستخدِم الحزمة الافتراضية، ويَعنِي ذلك أننا لم نُصرِّح بانتمائها إلى أي حزمةٍ مسماة. وفي المقابل، عند إنجاز أي برمجة جدية، فمن الأفضل دومًا أن نُنشِئ حزمةً لاحتواء الأصناف المُستخدَمة بالبرنامج.
</p>

<p>
	تُوصِي مؤسسة Oracle بأن تكون أسماء الحزم مبنيةً على <a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%81%D9%8A-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA%D8%9F-r573/" rel="">اسم نطاق domain name</a> الإنترنت للمؤسسة المُنتِجة للحزمة؛ فبالنسبة لحاسوب المؤلف، فإن اسم النطاق الخاص به هو <code>eck.hws.edu</code>، ولا يُفترَض لأي حاسوب آخر بالعالم عمومًا أن يكون له نفس الاسم؛ ووفقًا لمؤسسة Oracle، ينبغي أن يكون اسم الحزمة في تلك الحالة هو <code>edu.hws.eck</code>، أي سيكون ترتيب عناصر اسم النطاق معكوسًا. علاوةً على ذلك، ينبغي أن تُسمَى الحزم الفرعية ضمن تلك الحزمة بأسماءٍ، مثل <code>edu.hws.eck.mdbfx</code>، وهو في الواقع الاسم الذي اختاره المؤلف لتطبيق عرض مجموعة ماندلبرو. ويَضمَن ذلك ألا يَستخدِم أي شخصٍ آخر -شرط أن يتبِع نفس نمط التسمية- نفس اسم الحزمة، وبناءً على ذلك، يُمكِن استخدام ذلك الاسم الفريد لتحديد هوية التطبيق.
</p>

<p>
	ناقشنا باختصار طريقة استخدام الحزم بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-programming-environment-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1020/" rel="">بيئات البرمجة programming environment في جافا</a>، وكذلك أثناء شرح بعض الأمثلة البرمجية بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1487/" rel="">أمثلة برمجية على الشبكات في جافا: إطار عمل لتطوير الألعاب عبر الشبكة</a>. باختصار، يُمثِل التالي كل ما ينبغي أن تعرفه بخصوص تطبيق عرض مجموعة ماندلبرو، فالبرنامج مُعرَّفٌ ضمن 7 ملفات شيفرة مصدرية تجدها بالمجلد <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/" rel="external nofollow">edu/hws/eck/mdbfx</a> الموجود داخل مجلد source بالموقع؛ أي أن الملفات موجودة بمجلدٍ اسمه mdbfx، والموجود بدوره بمجلدٍ اسمه eck، المتواجد بمجلد hws، ضمن مجلد edu. لا بُدّ أن تتبِع المجلدات اسم الحزمة بتلك الطريقة.
</p>

<p>
	يحتوي نفس ذلك المجلد على ملفٍ اسمه <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/strings.properties" rel="external nofollow">strings.properties</a> المُستخدَم بالبرنامج والذي سنناقشه بالأسفل. ويحتوي مجلد examples على ملفات الموارد التي تَستخدِمها قائمة "Examples". ونظرًا لاعتماد البرنامج على مكتبة JavaFX، فينبغي أن تتأكّد من توفُّر المكتبة عند تصريف البرنامج أو تشغيله كما ناقشنا بمقال بيئات البرمجة (programming environment) في جافا المشار إليه في الأعلى؛ وإذا كنت تَستخدِم بيئة تطوير متكاملة Integrated Development Environment مثل Eclipse، فكل ما عليك فعله هو إضافة مجلد "edu" إلى المشروع، مع ضبطه لكي يَستخدِم مكتبة JavaFX؛ وإذا أردت استخدام سطر الأوامر، فينبغي أن يشير مجلد العمل working directory إلى المجلد المُتضمِّن لمجلد edu؛ وإذا لم تكن تَستخدِم إصدارًا قديمًا من JDK والذي تكون فيه مكتبة JavaFX مبنيةً مسبقًا، فستحتاج إلى إضافة خيارات مكتبة JavaFX إلى أمري <code>javac</code> و <code>java</code>.
</p>

<p>
	إذا كنت قد عرَّفت الأمرين <code>jfxc</code> و <code>jfx</code> المكافئين للأمرين <code>javac</code> و <code>java</code> مع خيارات مكتبة JavaFX، فيُمكِنك ببساطة أن تُصرِّف الشيفرة المصدرية باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_14" style="">
<span class="pln">jfxc  edu</span><span class="pun">/</span><span class="pln">hws</span><span class="pun">/</span><span class="pln">eck</span><span class="pun">/</span><span class="pln">mdbfx</span><span class="com">/*.java</span></pre>

<p>
	أو الأمر التالي إذا كنت تَستخدِم نظام التشغيل Windows:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_16" style="">
<span class="pln">jfxc  edu\hws\eck\mdbfx\*</span><span class="pun">.</span><span class="pln">java</span></pre>

<p>
	ستَجِد صنف التطبيق الرئيسي مُعرَّفًا بالصنف <code>Main</code>. اِستخدِم الأمر التالي لتشغيل البرنامج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_18" style="">
<span class="pln">jfx  edu</span><span class="pun">.</span><span class="pln">hws</span><span class="pun">.</span><span class="pln">eck</span><span class="pun">.</span><span class="pln">mdbfx</span><span class="pun">.</span><span class="typ">Main</span></pre>

<p>
	يجب أن يُنفِّذ هذا الأمر بالمجلد المُتضمِّن لمجلد edu؛ وإذا كان إصدار JDK المُستخدِم يتضمَّن مكتبة JavaFX مُسبَقًا، فيُمكِنك ببساطة أن تَستخدِم الأمرين <code>javac</code> و <code>java</code> بدلًا من <code>jfxc</code> و <code>jfx</code>.
</p>

<p>
	يتضمَّن الملف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/MandelbrotCanvas.java" rel="external nofollow">MandelbrotCanvas.java</a> غالبية العمليات المطلوبة لحساب صور مجموعة ماندلبرو وعرضها؛ إذ يُعدّ الصنف <code>MandelbrotCanvas</code> صنفًا فرعيًا من الصنف <code>Canvas</code>، ويُمكِنه حساب صور مجموعة ماندلبرو وعرضها كما ناقشنا بالأعلى. تعتمد الصورة الناتجة على النطاق الظاهر من قيم <code>x</code> و <code>y</code>، وعلى الحد الأقصى لعدد مرات تكرار الخوارزمية، وعلى لوحة الألوان المُستخدَمة لتلوين البكسلات خارج المجموعة. وتأتي جميع تلك المُدْخَلات من مكان آخر ضمن البرنامج، ويقتصر دور الصنف <code>MandelbrotCanvas</code> على حساب الصورة وعرضها بناءً على قيم المْدخَلات المُعطاة له.
</p>

<p>
	بالإضافة إلى تلوين بكسلات الصورة، يَستخدِم الصنف <code>MandelbrotCanvas</code> مصفوفةً ثنائية الأبعاد لتخزين قيمة <code>count</code> لكل بكسلٍ في الصورة؛ إذ تُحدِّد قيمة <code>count</code> لبكسل معين مع لوحة الألوان المُستخدَمة، اللون المستعمل لتلوين ذلك البكسل كما ناقشنا بالأعلى. وفي حالة تغيير لوحة الألوان المُستخدَمة، سيَستخدِم البرنامج قيمة المتغير <code>count</code> لكل بكسل لإعادة ضبط الألوان، دون أن يعيد حساب مجموعة ماندلبرو مرةً أخرى. في المقابل، إذا تغير نطاق قيم <code>x</code> و <code>y</code>، أو إذا تغير حجم النافذة، فسيضطّر البرنامج لإعادة حساب قيم <code>count</code> لجميع البكسلات.
</p>

<p>
	قد تستغرِق عملية حساب تلك القيم وقتًا طويلًا، ولأنه من غير المُفترَض أن نُعطِّل block واجهة المُستخدِم أثناء إجراء تلك الحسابات، سيُجرِي البرنامج تلك العمليات بخيطٍ عاملٍ worker thread منفصل كما ناقشنا بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/" rel="">البرمجة باستخدام الخيوط threads في جافا</a>، إذ يَستخدِم البرنامج تحديدًأ خيطًا عاملًا وحيدًا لكل معالج. عند بدء عملية حساب تلك القيم، ستكون الصورة شفافة، أي أنه يُمكِنك رؤية الخلفية الرمادية للنافذة.
</p>

<p>
	تُقسَّم العملية إجمالًا إلى مجموعةٍ من المهام tasks، وتتكوَّن كل مهمة من عملية حساب صف واحد من الصورة. وبعد انتهاء أي مهمة، تُطبِّق الألوان الناتجة على البكسلات الموجودة بالصف الخاص بتلك المهمة. نظرًا لأنه من الممكن تعديل الحاوية فقط من خلال خيط تطبيق JavaFX، فستَستدعِي كل مهمة التابع <code>Platform.runLater()‎</code> لإجراء التغييرات المطلوبة، وهذا يُمكِّن المُستخدِم من الاستمرار باستخدام القوائم وحتى الفأرة أثناء عملية حساب الصورة.
</p>

<p>
	يحتوي الملف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/MandelbrotPane.java" rel="external nofollow">MandelbrotPane.java</a> على كامل محتوى نافذة تطبيق عرض مجموعة ماندلبرو، إذ يُعدّ الصنف <code>MandelbrotPane</code> صنفًا فرعيًا من الصنف <code>BorderPane</code>. يحتوي منتصف كائن الحاوية المنتمي للصنف <code>BorderPane</code> على كائنٍ من النوع <code>MandelbrotCanvas</code>. وفي الحقيقة، يَستخدِم البرنامج حاويةً ثانيةً شفافةً فوق الحاوية المتضمِّنة للصورة؛ فعندما يَرسِم المُستخدِم "صندوق تكبير" باستخدام الفأرة، فإن ذلك الصندوق يُرسَم فعليًا بالحاوية العلوية لكي لا يشوه الصورة (ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%86-%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D8%A7%D8%AE%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%AC%D8%A7%D9%81%D8%A7-r1526/" rel="">أمثلة عن رسوميات فاخرة باستعمال جافا</a>). في المقابل، تحتوي المنطقة السفلية من الحاوية على عنوان من النوع <code>Label</code>، يَعمَل مثل شريطٍ لعرض حالة البرنامج، إذ يُستخدَم لعرض بعض المعلومات التي قد تَهِم المُستخدِم. أخيرًا، يحتوي البرنامج على شريط قوائم أعلى الحاوية.
</p>

<p>
	يُعرِّف الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/Menus.java" rel="external nofollow">Menus.java</a> -المُشتَق من الصنف <code>MenuBar</code>- شريط القوائم الخاص بالتطبيق. (ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150/" rel="">بناء تطبيقات كاملة باستعمال مكتبة جافا إف إكس JavaFX</a> لمزيدٍ من المعلومات عن القوائم وعناصر القوائم، كما يُعرِّف الصنف <code>Menus</code> مجموعةً من التوابع والأصناف الفرعية المتداخلة nested subclasses لتمثيل جميع عناصر القائمة، وكذلك الأوامر التي تُمثِّلها تلك العناصر؛ إذ تشتمل تلك الأوامر على أوامرٍ مُتعلِّقة بمعالجة الملفات وتَستخدِم التقنيات التي تَعرَّضنا لها بمقالات <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/" rel="">مدخل إلى التعامل مع الملفات في جافا</a> و<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D9%85%D8%AE%D8%AA%D8%B5%D8%B1%D8%A9-%D9%84%D9%84%D8%BA%D8%A9-xml-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D9%87%D8%A7-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1475/" rel="">مقدمة مختصرة للغة XML واستعمالها في تطبيقات جافا</a> وأمثلة عن رسوميات فاخرة باستعمال جافا المشار إليه بالأعلى.
</p>

<p>
	تحتوي القوائم "MaxIterations" و "Palette" و"PaletteLength" على مجموعةٍ من الكائنات المنتمية إلى النوع <code>RadioMenuItems</code>. يُعرِّف البرنامج صنفًا متداخلًا داخل الصنف <code>Menus</code> لتمثيل كل مجموعة؛ فعلى سبيل المثال، يُعرِّف الصنف <code>PaletteManager</code> عناصر قائمة "Palette" على هيئة متغيرات نسخة instance variables، ويُسجِّل معالج حدث لكل عنصر، كما يُعرِّف القليل من البرامج المفيدة لمعالجة القائمة. تتشابه الأصناف الخاصة بالقوائم الثلاثة، جتى أنه من الأفضل تعريفها على أنها أصنافٌ فرعيةٌ مشتقةٌ من صنفٍ أكثر عمومية. ويحتوي البرنامج أيضًا على قائمة "Examples" التي تتضمَّن الإعدادات الخاصة ببعض العينات لقطعٍ من مجموعة ماندلبرو.
</p>

<p>
	يُنفِّذ الصنف <code>MandelbrotPane</code> كثيرًا من العمل الذي يتطلّبه البرنامج؛ فهو يُهيئ معالجات لأحداث الفأرة <code>MousePressed</code> و <code>MouseDragged</code> و <code>MouseReleased</code> بالحاوية العلوية، ليُمكِّن المُستخدِم من تكبير الصورة وتصغيرها؛ كما يُهيئ معالجًا للحدث <code>MouseMoved</code>، الذي يُحدِّث شريط الحالة ويجعله يَعرِض إحداثيات النقطة المقابلة للمكان الحالي لمؤشر الفأرة على الصورة. يُولَّد الحدث <code>MouseMoved</code> عندما يُحرِّك المُستخدِم مؤشر الفأرة دون أن يضغط باستمرار على زرها. ويُستخدم كذلك الحدث <code>MouseExited</code> لإعادة ضبط شريط الحالة إلى كلمة "Idle" عندما يقع مؤشر الفأرة خارج الحاوية.
</p>

<p>
	بالإضافة إلى ما سبق، يُنفِّذ البرنامج أوامر قوائم أخرى كثيرة باستدعاء توابع مُعرَّفةٍ بالصنف <code>MandebrotPane</code>. ويحتوي الصنف <code>Menus</code> على متغير نسخة اسمه <code>owner</code>، يشير إلى الحاوية -من النوع <code>MandelbrotPane</code>- المُتضمِّنة لشريط القوائم، وبالتالي يُمكِنه استخدام ذلك المتغير لاستدعاء أي توابع مُعرَّفة بالصنف <code>MandelbrotPane</code>؛ إذ يضبُط التابع <code>setLimits()‎</code> مثلًا، نطاق قيم <code>x</code> و <code>y</code> الظاهرة بالصورة؛ كما أن هناك توابعٌ أخرى لضبط كُلٍ من لوحة الألوان المُستخدَمة وعدد الألوان الموجودة بلوحة الألوان، والحد الأقصى لعدد مرات تكرار الخوارزمية.
</p>

<p>
	بمجرد تغيُّر أي من تلك الخاصيات، لا بُدّ من تعديل الصورة المعروضة لمجموعة ماندلبرو؛ فعلى سبيل المثال، عندما تتغير لوحة الألوان المُستخدَمة أو عدد الألوان الموجودة باللوحة، يَحسِب الصنف <code>MandelbrotPane</code> لوحةً جديدةً من الألوان ويَستدعِي تابعًا مُعرَّفًا بالصنف <code>MandelbrotCanvas</code> ليُبلِّغه بأن عليه استخدام تلك اللوحة الجديدة. وفي المقابل، عندما يتغير الحد الأقصى لعدد مرات تكرار الخوارزمية، تكون إعادة حساب الصورة بالكامل ضرورية، ولهذا يَستدعِي الصنف <code>MandelbrotPane</code> التابع <code>startJob()‎</code> المُعرَّف بالصنف <code>MandelbrotCanvas</code> ليُبلِّغه بأن عليه أن يبدأ وظيفةً جديدة، ويتولى الصنف <code>MandelbrotCanvas</code> كل العمل اللازم لتهيئة تلك الوظيفة وإدارتها.
</p>

<p>
	يُمرَّر كائن الصنف <code>MandelbrotPane</code> المُستخدَم بالبرنامج مثل معاملٍ إلى باني الصنف <code>Menus</code>، ويُخزِّن بدوره كائن الصنف <code>Menus</code> المُمثِّل للقوائم نسخةً من ذلك الكائن بهيئة متغير نسخة، اسمه <code>owner</code>. في الواقع، يُعالِج الصنفان <code>MandelbrotPane</code> و <code>MandelbrotCanvas</code> غالبية أوامر القوائم، ولكي يتمكَّن الكائن المُمثِّل للقوائم من تنفيذ تلك الأوامر، فإنه يحتاج إلى مرجع reference إلى كائن الصنف <code>MandelbrotPane</code>. وبالمثل من الصنف <code>MandelbrotCanvas</code>، يُعرِّف كائن الصنف <code>MandelbrotPane</code> التابع <code>getDisplay()‎</code> الذي يعيد مرجعًا إلى الحاوية التي يحتويها، وبالتالي يستطيع الكائن المُمثِّل للقوائم الحصول على مرجعٍ إلى الحاوية باستدعاء <code>owner.getDisplay()‎</code>.
</p>

<p>
	كنا نضع شيفرة البرنامج بالكامل بالأمثلة السابقة من هذه السلسلة بملفٍ واحدٍ كبير، وبالتالي كانت جميع الكائنات متاحةً لكل أجزاء الشيفرة مباشرةً. وفي المقابل، عند تقسيم البرنامج إلى مجموعة من الملفات، لا يكون الوصول إلى الكائنات الضرورية بهذه السهولة.
</p>

<p>
	تُعدّ الأصناف <code>MandelbrotPane</code> و <code>MandelbrotCanvas</code> و <code>Menus</code> أكثر الأصناف أهميةً بتطبيق عرض مجموعة ماندلبرو؛ إذ يُعرِّف الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/Main.java" rel="external nofollow">Main.java</a> الصنف الفرعي المُشتَق من الصنف <code>Application</code>، والذي ينبغي تشغيله عند تنفيذ البرنامج؛ ويضع تابعه <code>start()‎</code> كائنًا من النوع <code>MandelbrotPane</code> داخل المرحلة <code>stage</code> الرئيسية للبرنامج.
</p>

<p>
	يحتوي البرنامج على ثلاثة أصناف أخرى، إذ يُعرِّف الصنفان <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/SetImageSizeDialog.java" rel="external nofollow">SetImageSizeDialog.java</a> و <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/SetLimitsDialog.java" rel="external nofollow">SetLimitsDialog.java</a> صناديق نوافذ مخصَّصة، والتي لن نناقشها هنا أكثر من ذلك؛ أما الصنف الأخير، فهو <code>I18n</code>، والذي سنناقشه بالأسفل.
</p>

<p>
	أظهرت هذه المناقشة القصيرة لتصميم تطبيق عرض مجموعة ماندلبرو أنه يَستخدِم تشكيلةً واسعةً من التقنيات التي تعرَّضنا لها مُسبقًا خلال مقالات هذه السلسلة، وسنفحص بالجزء المُتبقِي من هذا المقال القليل من الخاصيات الجديدة المُستخدَمة ضمن البرنامج.
</p>

<h2>
	الأحداث ومستمعي الأحداث والارتباطات
</h2>

<p>
	تعاملنا مع الأحداث events ومستمعي الأحداث بكثرة، وكذلك مع ارتباط binding الخاصيات القابلة للمراقبة observable ببعض الأمثلة، وسيكون من الرائع لو رأينا طريقة استخدام تلك التقنيات ضمن تطبيق عرض مجموعة ماندلبرو، إذ سنَستخدِم مجموعةً من الأصناف.
</p>

<p>
	لنبدأ الآن من الحقيقة التالية: لا يَعرِف الصنف <code>MandelbrotCanvas</code> أي شيء عن الصنف <code>Menus</code> مع أن شريط القوائم يحتوي على عناصر يبدو وكأنها تَعرِف ما يحدث بصنف الحاوية <code>MandelbrotCanvas</code>. بالتحديد، تُعطَّل بعض عناصر القائمة عندما تكون عملية حساب الصورة قيد التنفيذ.
</p>

<p>
	بما أن الحاوية لا تستدعِي أي توابع أو تَستخدِم أيًا من متغيرات صنف القوائم <code>Menus</code>، فكيف تمكَّنت القوائم من معرفة ما إذا كانت هناك عمليةً حسابيةً قيد التنفيذ بالحاوية؟ الإجابة بالطبع هي من خلال استخدام الأحداث أو على نحوٍ أكثر دقة، وذلك من خلال استخدام الارتباط (ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">البواني وتهيئة الكائنات Object Initialization في جافا</a>).
</p>

<p>
	يحتوي الصنف <code>MandelbrotCanvas</code> على خاصية قابلة للمراقبة من النوع <code>boolean</code> اسمها <code>working</code>، إذ تحتوي تلك الخاصية على القيمة <code>true</code> عندما يكون هناك عملية معالجة قيد التنفيذ. وينبغي أن تكون عناصر القائمة مُعطَّلةً عندما تكون قيمة تلك الخاصية مساويةً للقيمة <code>true</code>، وهو ما يُمكِننا إجراؤه بسطر شيفرةٍ واحد يربُط خاصية عنصر قائمة <code>disableProperty</code> بخاصية الحاوية <code>workingProperty</code>. على سبيل المثال، يُمكِننا تطبيق ذلك على عنصر القائمة "saveImage" بكتابة ما يَلي داخل باني الصنف <code>Menus</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_20" style="">
<span class="pln">saveImage</span><span class="pun">.</span><span class="pln">disableProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln">owner</span><span class="pun">.</span><span class="pln">getDisplay</span><span class="pun">().</span><span class="pln">workingProperty</span><span class="pun">());</span></pre>

<p>
	إذ يشير <code>owner</code> هنا إلى كائن الصنف <code>MandelbrotPane</code>؛ بينما تشير القيمة المُعادة من التابع <code>owner.getDisplay()‎</code> إلى كائن الصنف <code>MandelbrotCanvas</code> الموجود به.
</p>

<p>
	وبالمثل، يُعيد عنصر القائمة "Restore Previous Limits" ضبط نطاق قيم <code>x</code> و <code>y</code> الظاهرة إلى قيمها السابقة قبل آخر تحديث؛ إذ يَستخدِم الصنف <code>Menus</code> متغير النسخة <code>previousLimits</code> من النوع <code>double[]‎</code> ليتذكر نطاق القيم السابق، ولكن السؤال هو: كيف يحصل على تلك المعلومات؟ عندما يُكبّر المُستخدِم الصورة أو يُصغرّها، يحدث ذلك التغيير بالصنف <code>MandelbrotPane</code>؛ بالتالي لا بُدّ إذًا من وجود طريقة تُمكِّن القوائم من ملاحظة ذلك التغيير، ويكْمُن الحل طبعًا في استخدام خاصيةٍ قابلةٍ للمراقبة، وتكون تلك الخاصية من النوع <code>ObjectProperty&lt;double[]&gt;‎</code> في هذه الحالة.
</p>

<p>
	يُضيف باني الصنف <code>Menus</code> مستمع حدث من النوع <code>ChangeListener</code> إلى الخاصية <code>limitsProperty</code> المُعرَّفة بالصنف <code>MandelbrotPane</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_22" style="">
<span class="pln">owner</span><span class="pun">.</span><span class="pln">limitsProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="pln">o</span><span class="pun">,</span><span class="pln">oldVal</span><span class="pun">,</span><span class="pln">newVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="com">// خزِّن القيمة القديمة للمتغير‫ limitsProperty لاستخدامها بالأمر "Restore Previous Limits"</span><span class="pln">
    previousLimits </span><span class="pun">=</span><span class="pln"> oldVal</span><span class="pun">;</span><span class="pln">
    undoChangeOfLimits</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="pln"> previousLimits </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نظرًا لأننا نَستخدِم الأحداث هنا للتواصل، فإن الصنفين <code>MandelbrotCanvas</code> و <code>MandelbrotPane</code> خفيفا الترابط loosely coupled مع الصنف <code>Menus</code>. في الحقيقة، يُمكِننا استخدامهما دون أي تعديل ببرامج أخرى لا تحتوي على نفس الصنف <code>Menus</code> من الأساس؛ وبدلًا من استخدام الأحداث والارتباط، كان من الممكن جعل صنفي الحاوية والعرض يَستدعِيان توابعًا، مثل <code>limitsChanged()‎</code> و <code>computationStarted()‎</code> مُعرَّفين بالصنف <code>Menus</code>. يكون الترابط بين الأصناف في تلك الحالة قويًا strong coupling، وبالتالي سيضطّر أي مبرمج يرغب باستخدام الصنف <code>MandelbrotCanvas</code> إلى استخدام الصنف <code>Menus</code> أيضًا، أو إلى تعديل الصنف <code>MandelbrotCanvas</code> كي لا يُشير إلى الصنف <code>Menus</code>.
</p>

<p>
	لا يُمكِننا طبعًا معالجة جميع المشاكل باستخدام الأحداث، كما أنه ليس من الضروري أن يكون أي ترابط قوي شيئًا سيئًا، إذ يشير الصنف <code>MandelbrotPane</code> مثلًا إلى الصنف <code>MandelbrotCanvas</code> مباشرةً ولا يُمكِننا استخدامه بدونه، ولكن بما أن الغرض من كائن الصنف <code>MandelbrotPane</code> هو حمل كائنٍ آخر من الصنف <code>MandelbrotCanvas</code>، فلا يُمثِّل هذا الترابط مشكلةً هنا. وفي المقابل، يُمكِننا استخدام الصنف <code>MandelbrotCanvas</code> على نحوٍ مستقل عن الصنف <code>MandelbrotPane</code>.
</p>

<p>
	يُوظِّف الصنف <code>MandelbrotPane</code> الأحداث لغرضٍ آخر؛ إذ تقع حاوية الصورة والحاوية الشفافة ضمن الكائن <code>displayHolder</code> من النوع <code>StackPane</code>، وعندما يُغيّر المُستخدِم حجم النافذة، سيتغير حجم الكائن <code>displayHolder</code> ليتناسب مع الحجم الجديد، وينبغي عندها أن يُضبَط حجم الحاوية لكي يتناسب مع حجم العرض الجديد؛ أي لا بُدّ من بدء عملية المعالجة لحساب صورةٍ جديدة. ولذلك، يُهيئ الصنف <code>MandelbrotPane</code> مستمعين إلى الخاصيات <code>height</code> و <code>width</code> المُعرَّفة بالكائن <code>displayHolder</code>؛ وذلك حتى يتمكَّن من الاستجابة للتغييرات بالحجم.
</p>

<p>
	ولكن، عندما يُغيّر المُستخدِم حجم النافذة ديناميكيًا، قد يتغير حجم <code>displayHolder</code> عدة مرات بكل ثانية. ونظرًا لأن بدء عملية حساب صورة جديدة يتطلَّب كثيرًا من الوقت، فإننا بالتأكيد لا نرغب في فعل ذلك عدة مراتٍ بالثانية الواحدة.
</p>

<p>
	في الواقع، سيبدأ البرنامج عملية حساب صورة جديدة فقط بعد مرور حوالي ثلث ثانية من توقُّف التغيير بحجم النافذة؛ وبالتالي إذا جرَّبت تغيير حجم نافذة البرنامج، فستلاحظ أن الحاوية لا تُغيِّر حجمها تلقائيًا بتُغيُّر حجم النافذة، ويَعرِض البرنامج نفس الصورة طالما كان الحجم هو نفسه. تُوضِح الشيفرة التالية طريقة فعل ذلك.
</p>

<p>
	يُهيئ البرنامج مستمعي أحداث تغيُّر الحجم <code>displayHolder</code>، ليستدعوا التابع <code>startDelayedJob()‎</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_24" style="">
<span class="pln">displayHolder</span><span class="pun">.</span><span class="pln">widthProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> startDelayedJob</span><span class="pun">(</span><span class="lit">300</span><span class="pun">,</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
displayHolder</span><span class="pun">.</span><span class="pln">heightProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> startDelayedJob</span><span class="pun">(</span><span class="lit">300</span><span class="pun">,</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذ يُمثِّل المعامل الأول للتابع <code>startDelayedJob()‎</code> الزمن بوحدة الميلي ثانية، الذي لا بُدّ من انتظاره قبل إعادة ضبط حجم الحاوية وبدء عملية حساب جديدة؛ بينما يشير المعامل الثاني إلى أن ضبط حجم الحاوية ليس ضروريًا قبل بدء عملية المعالجة.
</p>

<p>
	يَستخدِم البرنامج كائنًا من النوع <code>Timer</code> المُعرَّف بحزمة <code>java.util</code> لكي يتمكَّن من تأجيل تنفيذ العملية؛ إذ يُمكِننا ببساطة أن نُمرِّر كائنًا من النوع <code>TimerTask</code> يُمثِّل مهمةً مؤجَلّةً إلى كائنٍ من النوع <code>Timer</code>، وذلك لكي تُنفَّذ المهمة بعد زمنٍ معين؛ كما يُمكِننا أيضًا إلغاء المهمة إذا لم يكن ذلك الزمن قد مرّ بعد، إذ يَستخدمِ البرنامج التابع <code>startDelayedJob()‎</code> لإضافة مهمة تغيير حجم الحاوية إلى المؤقت، لتُنفَّذ بعد 300 ميلي ثانية. وفي حالة استدعاء التابع <code>startDelayedJob()‎</code> مرةً أخرى قبل مرور 300 ميلي ثانية، فستُلغَى المهمة السابقة تلقائيًا وتُضاف المهمة الجديدة بدلًا منها إلى المؤقت. وبذلك، تكون مُحصلة ما سبق هو عدم تنفيذ أي مهمة إلى أن تَمرّ 300 ميلي ثانية دون أي استدعاء جديدٍ للتابع <code>()startDelayedJob</code>.
</p>

<p>
	يتيح البرنامج خيار ضبط الصورة لتكون ثابتة الحجم؛ إذ لا ينبغي تلك الحالة أن يتغير حجم <code>displayHolder</code> نهائيًا. وبناءً على ذلك، قد تكون الصورة صغيرةً، ولا تتمكَّن من ملئ النافذة بالكامل، وستَظهَر عندها أجزاءٌ من الخلفية الرمادية الموجودة وراءها. في المقابل، قد تكون الصورة كبيرةً جدًا على النافذة، وفي تلك الحالة، ينبغي أن تظهر أشرطة تمرير يُمكِن اِستخدامها للمرور عبر كامل الصورة؛ ويُنفِّذ البرنامج ذلك باستخدَام الصنف <code>ScrollPane</code> الذي يُمثِّل حاويةً تحتوي على مكوِّن واجهة معين، ويُوفِّر أشرطة تمرير إذا اقتضت الضرورة؛ أما عندما يكون حجم الصورة ثابتًا، فسيُحذَف <code>displayHolder</code> من الصنف <code>MandelbrotPane</code>، ويُوضَع بكائن الصنف <code>ScrollPane</code> المُمثِّل للحاوية، ثم يُوضَع كائن الحاوية ذلك بمنتصف كائن الصنف <code>MandelbrotPane</code>.
</p>

<h2>
	المزيد عن واجهات المستخدم الرسومية
</h2>

<p>
	أخيرًا، سنذكر هنا بعض التفاصيل المُتعلّقة ببرمجة واجهات المُستخدِم الرسومية التي لم نتمكَّن من عرضها بالمقالات السابقة.
</p>

<p>
	ذكرنا من قبل أن ملفات الموارد تُمثِّل جزءًا من البرنامج ولكنها لا تتضمَّن أي شيفرة، وقد رأينا على سبيل المثال في مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B9%D8%B6-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r1146/" rel="">التعرف على بعض أصناف مكتبة جافا إف إكس JavaFX</a> البسيطة مدى سهولة اِستخدَام الصور مثل ملفات موارد مع الصنف <code>Image</code>؛ ولكن يُمكِننا عمومًا استخدام أي نوعٍ من البيانات مثل ملفات موارد.
</p>

<p>
	يحتوي تطبيق عرض مجموعة ماندلبرو مثلًا، على قائمة "Examples"؛ وعندما يختار المُستخدِم أمرًا من تلك القائمة، تُحمَّل الإعدادات الخاصة بعرضٍ معيَّن لمجموعة ماندلبرو إلى البرنامج؛ وتكون هذه الإعدادات مُخزَّنةً في الواقع بهيئة ملفات موارد بصيغة <a href="https://www.bing.com/ck/a?!&amp;&amp;p=5c4e6b4feb1500706d5cff716067adc4c2913c42d86cf1d65eeec2d5e1f3077dJmltdHM9MTY1MDUyMjc4MCZpZ3VpZD0wOGFjNTliMy1hOWEwLTRhZmItYmFhNS01OTU1ZmM2YzE5ZmMmaW5zaWQ9NTE0Nw&amp;ptn=3&amp;fclid=e3b01bfd-c13c-11ec-b9e2-16d8489aa75f&amp;u=a1aHR0cHM6Ly9hY2FkZW15Lmhzb3ViLmNvbS9wcm9ncmFtbWluZy9qYXZhLyVEOSU4NSVEOSU4MiVEOCVBRiVEOSU4NSVEOCVBOS0lRDklODUlRDglQUUlRDglQUElRDglQjUlRDglQjElRDglQTktJUQ5JTg0JUQ5JTg0JUQ4JUJBJUQ4JUE5LXhtbC0lRDklODglRDglQTclRDglQjMlRDglQUElRDglQjklRDklODUlRDglQTclRDklODQlRDklODclRDglQTctJUQ5JTgxJUQ5JThBLSVEOCVBQSVEOCVCNyVEOCVBOCVEOSU4QSVEOSU4MiVEOCVBNyVEOCVBQS0lRDglQUMlRDglQTclRDklODElRDglQTctcjE0NzUvP21zY2xraWQ9ZTNiMDFiZmRjMTNjMTFlY2I5ZTIxNmQ4NDg5YWE3NWY&amp;ntb=1" rel="external nofollow">XML</a>.
</p>

<p>
	كيف يَصِل البرنامج إلى تلك الملفات؟ لسوء الحظ، ليس الأمر بنفس سهولة استخدام صورة على أنها ملف مورد لإنشاء كائنٍ من النوع <code>Image</code>.
</p>

<p>
	تُخزَّن الموارد بملفات تقع إلى جانب ملفات الأصناف المُصرَّفة الخاصة بالبرنامج؛ إذ يُحدِّد بلغة جافا كائنٌ من النوع <code>ClassLoader</code> -ويُعرَف باسم <strong>مُحمِّل أصناف class loader</strong>- مكان ملفات الأصناف ويُحمِّلها للبرنامج عند الحاجة. يملُك مُحمِّل الأصناف قائمةً بالمسارات التي ينبغي عليه البحث فيها عن ملفات الأصناف؛ إذ تُعرَف تلك القائمة باسم <strong>مسارات الأصناف class path</strong>، والتي تتضمَّن موضع تخزين أصناف جافا القياسية، كما تتضمَّن المجلد الحالي.
</p>

<p>
	إذا كان البرنامج مُخزَّنًا داخل ملف jar، فسيكون ذلك الملف أيضًا ضمن مسارات الأصناف. وبالإضافة إلى ملفات الأصناف، تستطيع كائنات الصنف <code>ClassLoader</code> العثور على ملفات الموارد الواقعة بمسارات الأصناف أو بمجلداتٍ فرعية داخل مسارات الأصناف.
</p>

<p>
	علينا أولًا أن نحصل على كائنٍ من النوع <code>ClassLoader</code> لكي نتمكَّن من استخدام ملفات الموارد؛ إذ يمكننا باستخدام ذلك الكائن أن نُحدِّد موضع ملف موردٍ معين. في العموم، يحتوي أي كائن على تابع النسخة <code>getClass()‎</code>، الذي يعيد كائنًا يُمثِّل الصنف الذي ينتمي إليه الكائن؛ ويحتوي الكائن المُمثِّل للصنف بدوره على التابع <code>getClassLoader()‎</code>، الذي يعيد الكائن -من النوع <code>ClassLoader</code>- الذي حمَّل الصنف المَعنِي. وبالتالي، يُمكِننا كتابة ما يلي بأي تابع نسخة لأي كائن لكي نحصل على مُحمِّل الأصناف الذي نريده.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_26" style="">
<span class="typ">ClassLoader</span><span class="pln"> classLoader </span><span class="pun">=</span><span class="pln"> getClass</span><span class="pun">().</span><span class="pln">getClassLoader</span><span class="pun">();</span></pre>

<p>
	يُمكِننا بدلًا من ذلك استخدام <code>ClassName.class</code>، إذ يشير <code>ClassName</code> إلى اسم الصنف الذي نريده، لكي نحصل على مرجع reference إلى الكائن المُمثِّل لذلك الصنف. على سبيل المثال، كان بإمكاننا استخدام <code>Menus.class.getClassLoader()‎</code> بتطبيق عرض مجموعة ماندلبرو لكي نسترجع مُحمِّل الأصناف.
</p>

<p>
	بمجرد حصولنا على مُحمِّل الأصناف، يُمكِننا اِستخدَامه للعثور على أي ملف مورد؛ إذ يعيد مُحمِّل الأصناف <a href="https://io.hsoub.com/programming/33663-%D9%85%D8%A7%D9%87%D9%88-%D8%B1%D8%A7%D8%A8%D8%B7-url-%D9%88%D9%85%D8%A7-%D8%A7%D9%87%D9%85%D9%8A%D8%AA%D9%87" rel="external">مُحدِّد الموارد الموحَّد URL</a> الخاص بالملف، وهو ما يُمكِّننا من قراءة البيانات الموجودة بالملف. وكما هو الحال مع ملفات الصور، يحتاج مُحمِّل الأصناف إلى مسار الملف لكي يتمكَّن من إيجاده؛ إذ يتضمَّن المسار اسم الملف، بالإضافة إلى أي مجلدات ينبغي التنقُل عبرها للوصول إلى الملف.
</p>

<p>
	تقع ملفات الموارد المُتضمِّنة للأمثلة بالنسبة لتطبيق عرض مجموعة ماندلبرو داخل سلسلة المجلدات <code>edu/hws/eck/mdbfx/examples</code>، وبالتالي يكون مسار أحد تلك الملفات "settings1.mdb" هو <code>edu/hws/eck/mdbfx/examples/settings1.mdb</code>.
</p>

<p>
	يُعيد الأمر التالي مُحدِّد الموارد المُوحد الخاص بذلك الملف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_28" style="">
<span class="pln">URL resourceURL </span><span class="pun">=</span><span class="pln"> 
         classLoader</span><span class="pun">.</span><span class="pln">getResource</span><span class="pun">(</span><span class="str">"edu/hws/eck/mdbfx/examples/settings1.mdb"</span><span class="pun">);</span></pre>

<p>
	والآن، بعد أن حصلنا على مُحدِّد الموارد الموحد الخاص بالملف، يُمكِننا ببساطة استخدام كائن مجرى من النوع <code>InputStream</code> لفتح الملف وقراءة بياناته على النحو التالي:
</p>

<pre class="ipsCode">
InputStream stream = resourceURL.openStream();
</pre>

<p>
	وهذا بالضبط هو ما يفعله تطبيق عرض مجموعة ماندلبرو لكي يُنفِّذ قائمة "Examples". إذًا، باستخدامنا لمجرى دخل، يُمكِننا قراءة أي نوعٍ من البيانات الموجودة بملف مورد، وأن نفعل بها أي شيء نريده، ولكن تذكّر أن جافا تُوفِّر أساليبًا أفضل لتحميل بعض أنواع البيانات، مثل الصور إلى البرنامج.
</p>

<p>
	سنناقش الآن موضوعًا آخر عن استخدام <strong>المُسرِّعات accelerators</strong> لعناصر القائمة؛ إذ أن المُسرِّع ببساطة هو مفتاح، أو عدة مفاتيح بلوحة المفاتيح يُمكِن اِستخدَامها لاستدعاء عنصر قائمة معين بدلًا من اختيار عنصر القائمة عن طريق الفأرة.
</p>

<p>
	يشيع استخدام المُسرِّع "Control-S" مثلًا لحفظ ملف؛ كما يُستخدَم الصنف <code>KeyCombination</code> المُعرَّف بحزمة <code>javafx.scene.input</code> لتمثيل اتحاد مجموعةٍ من المفاتيح المُمكِن اِستخدَامها مثل مُسرِّع؛ ويُمكِننا إنشاء كائنٍ من هذا الصنف من سلسلةٍ نصية، مثل "ctrl+S".
</p>

<p>
	ينبغي أن تحتوي السلسلة النصية على مجموعة عناصرٍ تفصل بينها إشارة الجمع، بحيث يُمثِّل كل عنصر منها -باستثناء الأخير- مفتاح مُعدِّل، مثل "ctrl"، أو "alt"، أو "meta"، أو "shift"، أو "shortcut"؛ ويُمكِن كتابة تلك المفاتيح باستخدام حروفٍ كبيرة أو صغيرة.
</p>

<p>
	يُمثِّل المفتاح "shortcut" استثناءً، إذ يُكافئ المفتاح "meta" <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">بنظام التشغيل</a> Mac؛ بينما يُكافئ المفتاح "ctrl" <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/?msclkid=22caac79c13d11ec8b68a05bcd1489d7" rel="">بأنظمة Linux</a> و Windows، وبالتالي نحصل على المُعدِّل المناسب لأوامر القائمة بحسب النظام المُشغَّل عليه البرنامج.
</p>

<p>
	في المقابل، لا بُدّ أن يكون العنصر الأخير بالسلسلة النصية المُمثِّلة لاتحاد مجموعة المفاتيح من نوع التعداد <code>KeyCode</code>؛ إذ يُمثِّل ذلك النوع في العموم حرفًا أبجديًا مكتوبًا بالحالة الكبيرة، ويُمثِّل مفتاح ذلك الحرف، ولكنه قد يكون أيضًا مفتاح دالة مثل "F9" (لا يَعمَل جميعها بالمناسبة). على سبيل المثال، تُمثِّل السلسلة النصية "ctrl+shift+N" الضغط باستمرار على مفتاحي "control" و "shift" مع الضغط على مفتاح "N"؛ بينما تُمثِّل السلسلة النصية "shortcut+S" الضغط باستمرار على المُعدِّل المناسب للحاسوب الذي يَعمَل عليه البرنامج مع الضغط على مفتاح "S".
</p>

<p>
	نستطيع تمرير تلك السلاسل النصية إلى التابع الساكن <code>KeyCombination.valueOf()‎</code> لنُنشِئ منها كائنًا من النوع <code>KeyCombination</code>، وذلك لنَستخدِمه لتهيئة مُسرِّعٍ لأي عنصرٍ من عناصر القائمة، إذ تضيف الشيفرة التالية مثلًا مُسرِّعًا لعنصر القائمة "Save Image" الموجود بتطبيق عرض مجموعة ماندلبرو:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_30" style="">
<span class="pln">saveImage</span><span class="pun">.</span><span class="pln">setAccelerator</span><span class="pun">(</span><span class="pln"> </span><span class="typ">KeyCombination</span><span class="pun">.</span><span class="pln">valueOf</span><span class="pun">(</span><span class="str">"shortcut+shift+S"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُمكِننا استخدام المُسرِّعات مع أي نوع من أنواع عناصر القائمة، بما في ذلك <code>RadioMenuItem</code> و <code>CheckMenuItem</code>. وفي جميع الحالات، عندما ينقر المُستخدِم على مجموعة المفاتيح المُمثِّلة لمُسرِّع معين، ينبغي أن يكون لذلك نفس تأثير النقر بالفأرة على عنصر القائمة المقابل.
</p>

<p>
	يكون المُسرِّع الخاص بعنصر قائمة معين مكتوبًا عادةً إلى جانب نص عنصر القائمة؛ وبالنسبة لتطبيق عرض مجموعة ماندلبرو، تملُك جميع الأوامر الموجودة بقائمتي "File" و "Control" مُسرِّعات خاصة بها.
</p>

<h2>
	التدويل Internationalization
</h2>

<p>
	سنناقش خلال ما هو متبقي من هذا المقال موضوعين يُمكِن تطبيقهما على جميع البرامج وليس على برامج واجهات المُستخدِم الرسومية فقط؛ إذ لا نُطبِّقهما عادةً بالبرامج الصغيرة، ولكنهما مهمان جدًا للتطبيقات الضخمة.
</p>

<p>
	يُقصَد بالتدويل كتابة البرنامج كتابةً يَسهُل معها تهيئته ليَعمَل بمختلف أنحاء العالم. تُستخدَم كلمة "I18n" عادةً للإشارة إلى التدويل، إذ يمثّل "18" عدد الأحرف بين الحرف الأول "I" والحرف الأخير "n" من كلمة "Internationalization". في المقابل، يُطلَق على مهمة تهيئة البرنامج ليَعمَل بمنطقة معينة اسم <strong>التوطين localization</strong>؛ في حين يُطلَق اسم <strong>المحليات locales</strong> على تلك المناطق. تختلف المحليات عن بعضها بجوانب كثيرة، مثل نوع العملة المُستخدَمة، والصيغة المُستخدَمة لكتابة الأعداد والتواريخ، ولكن الاختلاف الأبرز والأكثر وضوحًا هو اللغة. سنناقش هنا طريقة كتابة البرامج لنتمكَّن من ترجمتها إلى لغاتٍ أخرى بسهولة.
</p>

<p>
	تتمحور الفكرة الأساسية في عدم كتابة السلاسل النصية التي تَظهَر للمُستخدِم ضمن الشيفرة المصدرية للبرنامج؛ لأننا لو فعلنا ذلك، فسنحتاج إلى مترجم قادر على البحث داخل الشيفرة بأكملها، ويَستبدِل كل سلسلةٍ نصية بترجمتها، وسنضطّر بعدها إلى إعادة تصريف البرنامج. في المقابل، إذا أردنا كتابة برنامج يدعم خاصية التدويل، فلا بُدّ أن نُخزِّن كل السلاسل النصية معًا ضمن ملفٍ واحد أو أكثر على نحوٍ منفصلٍ تمامًا عن ملفات الشيفرة المكتوبة بلغة جافا، وبالتالي نستطيع أن نعثر عليهم بسهولة ونترجمهم. ونظرًا لأننا لم نُعدِّل ملفات الشيفرة، فإننا لا نحتاج حتى إلى إعادة تصريف البرنامج.
</p>

<p>
	والآن لكي نُنفِّذ تلك الفكرة، علينا أن نُخزِّن السلاسل النصية داخل <strong>ملف خاصيات properties file</strong> واحدٍ أو أكثر؛ وهو ملفٌ بسيط يحتوي على قائمة أزواج مفتاح / قيمة، ولأن الغرض منها هنا هو الترجمة، فستُشير القيم إلى السلاسل النصية المعروضة للمُستخدِم، وهي ببساطة السلاسل النصية التي ينبغي أن نترجمها؛ أما المفاتيح فهي أيضًا سلاسلٌ نصية، ولكن لا حاجة لترجمتها، لأنها لن تَظهَر أبدًا للمُستخدِم.
</p>

<p>
	نظرًا لأننا لن نُعدِّل المفاتيح، يُمكِننا استخدامها ضمن الشيفرة المصدرية. وينبغي عمومًا أن يُقابِل كلُّ سلسلةٍ نصيةٍ مفتاحًا فريدًا يُعرِّف هويتها، وبناءً على ذلك، يستطيع البرنامج استخدام المفاتيح للعثور على ما يقابلها من سلاسل نصية من ملف الخاصيات؛ أي يحتاج البرنامج إلى معرفة المفاتيح فقط، بينما يرى المُستخدِم القيم المقابلة لتلك المفاتيح. وعند ترجمة ملف الخاصيات، سيتمكَّن المُستخدِم من رؤية قيمٍ مختلفة لنفس المفاتيح.
</p>

<p>
	تُكتَب أزواج مفتاح / قيمة بملفات الخاصيات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_32" style="">
<span class="pln">key</span><span class="pun">.</span><span class="pln">string</span><span class="pun">=</span><span class="pln">value string</span></pre>

<p>
	لا ينبغي أن تحتوي السلسلة النصية المُمثِّلة للمفتاح قبل إشارة التساوي على أيّ فراغات، إذ تُستخدَم عادةً النقاط للفصل بين الكلمات المُكوِّنة لها. في المقابل، بإمكان السلسلة النصية المُمثِّلة للقيمة أن تحتوي على مسافات أو أيّ محارف أخرى. إذا انتهى السطر بمحرف "\"، ستستمر القيمة إلى السطر التالي، وتُهمَل الفراغات الموجودة ببداية ذلك السطر في تلك الحالة. لسوء الحظ، يمكن أن يحتوي ملف الخاصيات على محارف من مجموعة محارف ASCII فقط؛ إذ تدعم تلك المجموعة الأحرف الأبجدية الإنجليزية فقط. ومع ذلك، بإمكان القيمة أن تتضمَّن محارف UNICODE عشوائية، مما يَعنِي ضرورة تشفير المحارف من خارج مجموعة محارف ASCII.
</p>

<p>
	يتضمَّن JDK البرنامج native2ascii، الذي يَستطيع تحويل الملفات التي تَستخدِم محارف من خارج مجموعة محارف ASCII إلى ملف خاصيات بصيغةٍ مناسبة.
</p>

<p>
	لنفترض أننا نريد عرض <a href="https://www.bing.com/ck/a?!&amp;&amp;p=0a2131f65b34ee514fd68eb3ede08ae52fa8c08a4d167c43151ea5069934669dJmltdHM9MTY1MDUyMTU4MSZpZ3VpZD1jMTMzYjFkYi0zNDkxLTQwMWUtOGNlNi1jNTEzYjJkMmI5MWYmaW5zaWQ9NTE2NQ&amp;ptn=3&amp;fclid=1887a7bd-c13a-11ec-b296-2dc3bbd523a6&amp;u=a1aHR0cHM6Ly9hY2FkZW15Lmhzb3ViLmNvbS9wcm9ncmFtbWluZy9qYXZhLyVEOCVBNyVEOSU4NCVEOCVCMyVEOSU4NCVEOCVBNyVEOCVCMyVEOSU4NC0lRDglQTclRDklODQlRDklODYlRDglQjUlRDklOEElRDglQTktc3RyaW5nLSVEOSU4OCVEOCVBNyVEOSU4NCVEOCVBMyVEOCVCNSVEOSU4NiVEOCVBNyVEOSU4MS1jbGFzcy0lRDklODglRDglQTclRDklODQlRDklODMlRDglQTclRDglQTYlRDklODYlRDglQTclRDglQUEtb2JqZWN0LSVEOSU4OCVEOCVBNyVEOSU4NCVEOCVBOCVEOCVCMSVEOCVBNyVEOSU4NSVEOCVBQy0lRDglQTclRDklODQlRDklODElRDglQjElRDglQjklRDklOEElRDglQTktc3Vicm91dGluZS0lRDklODElRDklOEEtJUQ4JUFDJUQ4JUE3JUQ5JTgxJUQ4JUE3LXIxMDE3Lz9tc2Nsa2lkPTE4ODdhN2JkYzEzYTExZWNiMjk2MmRjM2JiZDUyM2E2&amp;ntb=1" rel="external nofollow">سلسلة نصية</a> للمُستخدِم، مثل اسم أمر ضمن قائمة ببرنامج معين. عندها، سيحتوي إذًا ملف الخاصيات على زوج مفتاح/قيمة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_34" style="">
<span class="pln">menu</span><span class="pun">.</span><span class="pln">saveimage</span><span class="pun">=</span><span class="typ">Save</span><span class="pln"> PNG </span><span class="typ">Image</span><span class="pun">...</span></pre>

<p>
	إذ أن "Save PNG Image…‎" هي السلسلة النصية التي ينبغي أن تَظهَر بالقائمة. والآن، يستطيع البرنامج استخدام المفتاح "menu.saveimage" للعثور على قيمة المفتاح، ومن ثُمَّ يَستخدِمها مثل نص عنصر القائمة. تُجرَى عملية العثور تلك بواسطة الصنف <code>ResourceBundle</code>، إذ يمكنه استرجاع ملفات الخاصيات واستخدامها.
</p>

<p>
	قد تحتوي السلسلة النصية المعروضة للمُستخدِم -في بعض الأحيان- على سلاسل نصية فرعية لا يُمكِن تحديدها قبل تشغيل البرنامج، مثل اسم ملف معين؛ فقد يرغب البرنامج بإبلاغ المُستخدِم مثلًابالرسالة التالية "Sorry, the file, <strong>filename</strong>, cannot be loaded"، علمًا أن <strong>filename</strong> هو اسم ملفٍ اختاره المُستخدِم أثناء تشغيل البرنامج. لمعالجة تلك الحالة، بإمكان القيم -بملفات الخاصيات- أن تتضمَّن عنصرًا زائفًا placeholder؛ إذ يُستبدَل ذلك العنصر بسلسلةٍ نصيةٍ يُحدِّدها البرنامج بعد تشغيله، ويُكْتَب ذلك العنصر الزائف على النحو التالي "{0}"، أو "{1}"، أو "{2}".
</p>

<p>
	بالنسبة لمثال خطأ الملف، قد يحتوي ملف الخاصيات على القيمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_37" style="">
<span class="pln">error</span><span class="pun">.</span><span class="pln">cantLoad</span><span class="pun">=</span><span class="typ">Sorry</span><span class="pun">,</span><span class="pln"> the file</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="lit">0</span><span class="pun">},</span><span class="pln"> cannot be loaded</span></pre>

<p>
	يسترجِع البرنامج القيمة المقابلة للمفتاح <code>error.cantLoad</code>، ثم يستبدل اسم الملف الفعلي بالعنصر الزائف "{0}". قد يختلف ترتيب الكلمات عند ترجمة السلسلة النصية، ولكن نظرًا لأننا نَستخدِم عنصرًا زائفًا لتمثيل اسم الملف، سيتمكَّن المترجم من وضع اسم الملف بالمكان النحوي الصحيح بالنسبة للغة المُستخدَمة. في الواقع، لا يُعالِج الصنف <code>ResourceBundle</code> عملية الاستبدال تلك، وإنما يتولَّى الصنف <code>MessageFormat</code> تلك المهمة.
</p>

<p>
	يَستخدِم تطبيق عرض مجموعة ماندلبرو ملف خاصيات، اسمه <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/strings.properties" rel="external nofollow">strings.properties</a>؛ إذ لا بُدّ أن ينتهي اسم أي ملف خاصيات بكلمة ".properties". يقرأ البرنامج أي سلسلة نصية تراها عند تشغيل التطبيق من ذلك الملف.
</p>

<p>
	بَرمَج الكاتب الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/I18n.java" rel="external nofollow">I18n.java</a> لقراءة قيم المفاتيح، ويحتوي ذلك الصنف على التابع الساكن static التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_39" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> tr</span><span class="pun">(</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> key</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">...</span><span class="pln"> args </span><span class="pun">)</span></pre>

<p>
	يُعالِج التابع السابق العملية بالكامل؛ إذ يَستقبِل التابع المعامل <code>key</code> الذي يُمثِّل المفتاح الذي ينبغي أن يبحث عنه التابع ضمن ملف الخاصيات <code>strings.properties</code>؛ بينما تُمثِل المعاملات الإضافية القيم التي ينبغي أن تحلّ محل العناصر المزيفة -إن وجدت- بالقيمة المقابلة للمفتاح. تذكّر أن التصريح عن المعامل باستخدام "Object…‎"، وهذا يَعنِي احتمالية تمرير أيّ عددٍ من المعاملات الفعلية بعد المعامل <code>key</code>. ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-arrays-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1157/" rel="">تعرف على المصفوفات (Arrays) في جافا</a>. تشمل الاستخدامات النموذجية ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_41" style="">
<span class="typ">String</span><span class="pln"> saveImageCommandText </span><span class="pun">=</span><span class="pln"> I18n</span><span class="pun">.</span><span class="pln">tr</span><span class="pun">(</span><span class="pln"> </span><span class="str">"menu.saveimage"</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

</span><span class="typ">String</span><span class="pln"> errMess </span><span class="pun">=</span><span class="pln"> I18n</span><span class="pun">.</span><span class="pln">tr</span><span class="pun">(</span><span class="pln"> </span><span class="str">"error.cantLoad"</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> selectedFile</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	في الواقع، سترى استدعاءاتٍ كثيرةً مشابهة ضمن شيفرة تطبيق عرض مجموعة ماندلبرو؛ إذ كُتِبَ الصنف <code>I18n</code> بطريقة عامة لتتمكَّن من استخدامه بأي برنامجٍ آخر، وكل ما تحتاج إليه هو توفير ملف خاصيات على أنه ملف مورد، وأن تُخصِّص اسمه بالملف <code>I18n.java</code>، وأخيرًا أن تضع الصنف ضمن الحزمة الخاصة بك.
</p>

<p>
	يُمكِننا أيضًا استخدام أكثر من ملف خاصيات ضمن نفس البرنامج. فعلى سبيل المثال، قد نُضمِّن نسخةً فرنسية وأخرى يابانية من ملف الخاصيات إلى جانب النسخة الإنجليزية، فإذا كان اسم الملف بالنسخة الإنجليزية هو <code>strings.properties</code>، فينبغي أن تكون أسماء الملفات بالنسختين الفرنسية واليابانية <code>strings_fr.properties</code> و <code>strings_ja.properties</code>؛ إذ تملُك كل لغة ترميزًا مكوَّنًا من حرفين، مثل "fr" و "ja"، ويُستخدَم هذا الترميز باسم ملف الخاصيات الخاص بتلك اللغة.
</p>

<p>
	بدايةً، يَستخدِم البرنامج الاسم البسيط لملف الخاصيات "strings"؛ فإذا كان البرنامج مُشغَّلًا بنظام جافا وكانت اللغة المُفضلة هي الفرنسية، فسيحاول البرنامج أن يُحمِّل ملف خاصيات باسم "strings_fr.properties"؛ وإذا فشل، فسيحاول أن يُحمِّل ملف خاصيات باسم "strings.properties". يَعنِي ذلك، أن البرنامج سيستَخدِم ملف الخاصيات الخاص باللغة الفرنسية في الموضع الفرنسي، وسيستخدِم ملف الخاصيات الخاص باللغة اليابانية في موضع اللغة اليابانية، وأخيرًا، سيَستخدِم ملف الخاصيات الافتراضي في الحالات الأخرى.
</p>

<h2>
	الإعدادات المفضلة
</h2>

<p>
	تَسمَح غالبية البرامج للمُستخدِم بضبط إعداداته المفضلة؛ إذ تُمثِّل تلك الإعدادات جزءًا من حالة البرنامج، التي ينبغي أن يتذكرها عند تشغيله مرةً أخرى، وليتمكَّن من ذلك، عليه أن يُخزِّنها بملفٍ ضمن المجلد الرئيسي للمُستخدِم، كما أن عليه أن يتمكَّن من تحديد موقعها بعد ذلك.
</p>

<p>
	ينبغي إذًا تسمية الملف بطريقة تُجنِّبنا أيّ تعارضٍ مع أسماء الملفات المُستخدَمة بواسطة البرامج الأخرى. هناك مشكلةٌ أخرى، وهي أننا بتلك الطريقة سنملأ المجلد الرئيسي للمُستخدِم بملفاتٍ لا ينبغي أن يُعرَف بوجودها أساسًا.
</p>

<p>
	تتعامل جافا مع تلك المشاكل بتوفير طريقةٍ قياسية لمعالجة الإعدادات المفضلة؛ إذ تُعرِّف جافا الصنف <code>Preferences</code> ضمن حزمة <code>java.util.prefs</code>، وهذا الصنف هو كلُّ ما تحتاج إليه.
</p>

<p>
	يحتوي ملف تطبيق عرض مجموعة ماندلبرو <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/Main.java" rel="external nofollow">Main.java</a> على مثالٍ لاستخدام الصنف <code>Preferences</code>. يضبُط المُستخدِم عادةً إعداداته المفضلة بمعظم البرامج عبر صندوق نافذة مُخصَّص، ولكن لا يحتوي تطبيق ماندلبرو على إعدادات يتناسب معها هذا النوع من المعالجة. وبدلًا من ذلك، يُخزِّن البرنامج بعضًا من جوانب حالة البرنامج أوتوماتيكيًا على أنها إعداداتٌ مفضلة؛ وبكل مرة يُشغِّل المُستخدِم بها البرنامج، فإنه يقرأ تلك الإعدادات إن وُجدت؛ وبكل مرة يُغلِق المُستخدِم بها البرنامج، فإنه يُخزِّن تلك الإعدادات. تؤدي الحاجة إلى حفظ الإعدادات المفضلة إلى مشكلةٍ شيقةٍ نوعًا ما.
</p>

<p>
	ينتهي البرنامج بمجرد غلق النافذة، ولكننا نحتاج إلى طريقةٍ لحفظ الإعدادات عند حدوث ذلك، ويَكْمُن الحل عادةً في استخدام الأحداث: يُسجِّل التابع <code>start()‎</code> المُعرَّف بالصنف <code>Main</code> مستمعًا إلى حدث غلق النافذة، وعند وقوع ذلك الحدث، يُعالِجه معالج الحدث بحفظ الإعدادات المفضلة.
</p>

<p>
	تُخزَّن الإعدادات المفضلة ببرامج جافا بصيغةٍ تعتمد على المنصة المُستخدَمة، وبمكانٍ يعتمد أيضًا على تلك المنصة، ولكن بصفتك مبرمج جافا، فلا حاجة للقلق بشأن ذلك، إذ يعرف نظام التفضيلات بجافا تمامًا مكان تخزين البيانات، ولكن ما يزال هناك مشكلة فصل الإعدادات المفضلة لبرنامج معين عن إعدادات بقية برامج جافا التي قد تكون مُشغَّلة على نفس الحاسوب.
</p>

<p>
	تحلّ جافا تلك المشكلة بنفس الطريقة التي حلّت بها مشكلة تسمية الحزم؛ إذ تُعرَّف ببساطة الإعدادات المفضلة لبرنامج معين من خلال اسم الحزمة الخاصة بالبرنامج مع اختلافٍ بسيط بالترميز. على سبيل المثال، تطبيق عرض مجموعة ماندلبرو مُعرَّف بحزمة <code>edu.hws.eck.mdbfx</code>، وإعداداته مُعرَّفة عبر السلسلة النصية "‎/edu/hws/eck/mdbfx". حلَّ المحرف "/" محلَّ النقاط مع إضافة "/" إلى البداية.
</p>

<p>
	تُخزَّن الإعدادات المفضلة لأي برنامج داخل عقدة node، ويُمكِن استرجاع العقدة المقابلة للسلسلة النصية المُعرِّفة لإعدادات برنامج معين على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8247_43" style="">
<span class="typ">Preferences</span><span class="pln"> root </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Preferences</span><span class="pun">.</span><span class="pln">userRoot</span><span class="pun">();</span><span class="pln">
</span><span class="typ">Preferences</span><span class="pln"> node </span><span class="pun">=</span><span class="pln"> root</span><span class="pun">.</span><span class="pln">node</span><span class="pun">(</span><span class="pln">pathName</span><span class="pun">);</span></pre>

<p>
	يشير المعامل <code>pathname</code> إلى السلسلة النصية المُعرِّفة للعقدة، مثل "‎/edu/hws/eck/mdbfx". تتكوَّن العقدة نفسها من قائمةٍ بسيطة من أزواج مفتاح / قيمة، ويتكوَّن كلٌ من المفتاح والقيمة من سلاسل نصية. في الحقيقة، يستطيع البرنامج أن يُخزِّن أي سلاسل نصية ضمن تلك العقد، فهي تُمثِّل فقط طريقةً للاحتفاظ بالبيانات من عملية تشغيل برنامج لأخرى، ولكن يُحدِّد المفتاح عمومًأ هوية عنصرٍ معيّنٍ من الإعدادات المفضلة، وتكون قيمته المقابلة هي القيمة المفضلة. إذا كان <code>prefnode</code> كائنًا من النوع <code>Preferences</code>، فإنه يحتوي على التابع <code>prefnode.get(key)‎</code> لاسترجاع القيمة المرتبطة بمفتاح معين، والتابع <code>prefnode.put(key,value)‎</code> لضبط القيمة الخاصة بمفتاح معين.
</p>

<p>
	يَستخدِم البرنامج "Main.java" الإعدادات المفضلة لتخزين شكل نافذة البرنامج وموضعها، إذ يَسمَح ذلك بالإبقاء على حجم النافذة وشكلها بين عمليات التشغيل المتتالية لنفس البرنامج. وعندما يُشغِّل المُستخدِم البرنامج، سيجد النافذة بنفس المكان الذي تركها فيه آخر مرة شغَّل خلالها البرنامج. يُخزِّن البرنامج أيضًا اسم المجلد الذي كان يحتوي على آخر ملف فتحه المُستخدِم أو حفظه، وهذا في الواقع أمر مهم؛ لأن السلوك الافتراضي لصندوق نافذة فتح ملف هو عرض مجلد العمل الحالي، وهو نادرًا ما سيكون المكان الذي يرغب المُستخدِم بحفظ ملفات البرنامج به.
</p>

<p>
	بفضل خاصية الإعدادات المفضلة، سيتمكَّن المُستخدِم من الانتقال إلى المجلد الصحيح عند أول تعاملٍ له مع البرنامج، وبعد ذلك سيجد المُستخدِم نفسه بالمجلد الصحيح أوتوماتيكيًا عند استخدامه للبرنامج مرةً أخرى. تستطيع الإطلاع على الشيفرة المصدرية بالملف <a href="https://math.hws.edu/javanotes/source/chapter13/edu/hws/eck/mdbfx/Main.java" rel="external nofollow">Main.java</a> لمزيدٍ من التفاصيل.
</p>

<p>
	يُمكِننا بالتأكيد قول المزيد عن لغة جافا وعن البرمجة عمومًأ، ولكن هذه السلسلة لا تمثِّل سوى مقدمةً إلى البرمجة باستخدام <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/?msclkid=815c57aec13d11ec84a6ef8c745591a4" rel="">لغة جافا</a>، وقد حان الوقت لانتهاء رحلتنا. التي نتمنى أنها كانت رحلةً مُبهِجةً لك وأنك قد استفدت منها ببناء أساسٍ قوي تستطيع استخدامه قاعدةً لاكتشافاتك التالية.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="https://math.hws.edu/javanotes/c13/s5.html" rel="external nofollow">Section 5: Finishing Touches</a> من فصل Chapter 13: GUI Programming Continued من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%86%D9%88%D8%A7%D9%81%D8%B0-%D9%88%D8%B5%D9%86%D8%A7%D8%AF%D9%8A%D9%82-%D8%A7%D9%84%D9%86%D8%A7%D9%81%D8%B0%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1538/" rel="">النوافذ وصناديق النافذة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1525/" rel="">الخاصيات والارتباطات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1308/" rel="">كيفية كتابة برامج صحيحة باستخدام لغة جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%B5%D8%AD%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D9%88%D9%85%D8%AA%D8%A7%D9%86%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1291/" rel="">مقدمة إلى صحة البرامج ومتانتها في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1539</guid><pubDate>Fri, 22 Apr 2022 15:09:02 +0000</pubDate></item><item><title>&#x627;&#x644;&#x646;&#x648;&#x627;&#x641;&#x630; &#x648;&#x635;&#x646;&#x627;&#x62F;&#x64A;&#x642; &#x627;&#x644;&#x646;&#x627;&#x641;&#x630;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%86%D9%88%D8%A7%D9%81%D8%B0-%D9%88%D8%B5%D9%86%D8%A7%D8%AF%D9%8A%D9%82-%D8%A7%D9%84%D9%86%D8%A7%D9%81%D8%B0%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1538/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/6260f160f3680_--.png.2940c88f59c1fe7fe7e499e443bb983b.png" /></p>

<p>
	تتكوَّن جميع برامج <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">واجهات المُستخدِم الرسومية GUI</a> التي تعرَّضنا إليها حتى الآن من نافذةٍ واحدة، ولكن غالبية البرامج تتكوَّن في الواقع من عدة نوافذ. ولذلك سنناقش في هذا المقال طريقة إدارة التطبيقات متعددة النوافذ؛ كما سنتناول صناديق النافذة dialog boxes، وهي نوافذٌ صغيرة تظهر وتختفي، وتُستخدَم عادةًً للحصول على مُدْخَلٍ من المُستخدِم. بالإضافة إلى ذلك، سنتعرَّض للصنف <code>WebView</code>، وهو أداة تحكُّم <a ahref="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B9%D8%B6-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r1146/?msclkid=2bf3e0c1c13411ecb4ded8f3e9c243a3" rel="">بمكتبة JavaFX</a>، وتدعم كثيرًا وظائف متصفح الإنترنت.
</p>

<h2>
	صناديق النافذة Dialog Boxes
</h2>

<p>
	أيّ صندوق نافذة هو ببساطة نافذة تعتمد على نافذةٍ أخرى تَعمَل بمثابة أبٍ أو مالكٍ لصندوق النافذة؛ ويَعنِي ذلك، أنه لو أغلقنا النافذة المالكة، فسيُغلَق صندوق النافذة أوتوماتيكيًا.
</p>

<p>
	قد يكون صندوق النافذة شرطيًا modal أو غير شرطي modeless؛ فعند فتح صندوق نافذة شرطي، ستُعطَّل النافذة المالكة له، ولا يستطيع المُستخدِم التفاعل معها حتى يُغلِق صندوق النافذة. هناك أيضًا صناديق نافذة شرطية على مستوى التطبيق application modal؛ أي أنها تُعطِّل التطبيق بالكامل وليس فقط النافذة المالكة لها، وتُعدّ غالبية صناديق النافذة بمكتبة JavaFX من هذا النوع. تظهر صناديق النافذة الشرطية عادةً أثناء تنفيذ البرنامج، وذلك لكي تطلُب من المُستخدِم مُدْخَلًا معينًا، أو فقط لعرض رسالةٍ للمُستخدِم أحيانًا.
</p>

<p>
	في المقابل، لا تُعطِّل صناديق النافذة غير الشرطية <a href="https://academy.hsoub.com/entrepreneurship/ecommerce/%D9%83%D9%8A%D9%81-%D9%8A%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D9%82%D9%8A%D8%A7%D8%B3-%D8%AA%D9%81%D8%A7%D8%B9%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%B9%D9%84%D9%89-%D8%AE%D9%81%D8%B6-%D9%85%D8%B9%D8%AF%D9%84%D8%A7%D8%AA-%D9%86%D9%81%D9%88%D8%B1%D9%87%D9%85-r852/?msclkid=a0bf019bc13311ecb02ba0f9b326e620" rel="">تفاعل المُستخدِم</a> مع نوافذها المالكة، ولكنها ما تزال تُغلَق أوتوماتيكيًا عند غلق النافذة المالكة. يُستخدَم هذا النوع عادةً لإظهار عرضٍ view مختلفٍ للبيانات الموجودة بالنافذة المالكة، أو لعرض أدوات تحكُّم إضافية تؤثر على النافذة المالكة.
</p>

<p>
	يُمكِننا ضبط مرحلةٍ من الصنف <code>Stage</code> لتَعمَل مثل صندوق نافذة، ولكن غالبية صناديق النافذة ببرامج JavaFX هي كائناتٌ مُنتمية إلى الصنف <code>Dialog</code> المُعرَّف بحزمة <code>javafx.scene.control</code>، أو أي من أصنافه الفرعية subclasses. فإذا كان <code>dlg</code> كائن صندوق نافذة من النوع <code>Dialog</code>، فسيتضمَّن <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-objects-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-methods-%D9%88%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-variables-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1108/" rel="">توابع النسخ instance methods</a> التالية لعرض صندوق النافذة: <code>dlg.show()‎</code> و <code>dlg.showAndWait()‎</code>.
</p>

<p>
	إذا استخدمنا التابع <code>dlg.showAndWait()‎</code> لعرض الصندوق، فسيكون صندوق النافذة شرطيًا، حتى على مستوى التطبيق. لا يعود التابع <code>showAndWait()‎</code> إلى أن يُغلَق صندوق النافذة، وتكون بالتالي قيمة مُدْخَل المُستخدِم متاحةً بعد استدعاء <code>showAndWait()‎</code> مباشرةً. في المقابل، إذا استخدمنا التابع <code>dlg.show()‎</code> لعرض الصندوق، فسيكون غير شرطي، إذ يعود التابع <code>show()‎</code> فورًا، ويستطيع المُستخدِم بالتالي التعامل مع النافذة والصندوق، والتبديل بينهما. وفي الواقع، يتشابه استخدام صناديق النافذة غير الشرطية مع البرمجة على التوازي parallel programming نوعًا ما؛ فهناك شيئان قيد التنفيذ بنفس الوقت. سنُركّز هنا على صناديق النافذة الشرطية فقط.
</p>

<p>
	يُعدّ الصنف <code>Dialog&lt;T&gt;‎</code> نوعًا ذا معاملاتٍ غير مُحدّدة النوع parameterized، إذ يُمثِّل معامل النوع type parameter نوع القيمة التي سيعيدها التابع <code>showAndWait()‎</code>، والتي تكون تحديدًا من النوع <code>Optional&lt;T&gt;‎</code>؛ ويَعنِي ذلك أنها قيمةٌ من النوع <code>T</code>، ولكنها قد تكون موجودةً أو غير موجودة.
</p>

<p>
	يتضمَّن الصنف <code>Optional</code> -المُعرَّف بحزمة <code>java.util</code>- التابع <code>isPresent()‎</code>، والذي يُعيد قيمةً من النوع <code>boolean</code>، التي تشير إلى ما إذا كانت القيمة موجودةً أم لا، كما يتضمَّن التابع <code>get()‎</code> الذي يعيد تلك القيمة إذا كانت موجودة. إذا لم تكن القيمة موجودةً، فسيؤدي استدعاء التابع <code>get()‎</code> إلى حدوث استثناءٍ exception؛ ويَعنِي ذلك أنه إذا أردنا استخدام القيمة المعادة من التابع <code>showAndWait()‎</code>، فعلينا أولًا استدعاء التابع <code>isPresent()‎</code> لنتأكّد من أن التابع قد أعاد قيمةً فعلًا.
</p>

<p>
	يحتوي أي صندوق نافذة عادةً على زرٍ واحدٍ أو أكثر لغلق الصندوق على الأقل، وتكون أسماء تلك الأزرار غالبًا هي: "OK" أو "Cancel" أو "Yes" أو "No". كما يُستخدَم نوع التعداد <code>ButtonType</code> لتمثيل الأزرار الأكثر شيوعًا، ويتضمَّن القيم <code>ButtonType.OK</code> و <code>ButtonType.CANCEL</code> و <code>ButtonType.YES</code> و <code>ButtonType.NO</code>؛ إذ يُعدّ النوع <code>ButtonType</code> القيمة المعادة الأكثر شيوعًا من صناديق النافذة المُمثَلة بالصنف <code>Dialog</code>، وذلك للإشارة إلى الزر الذي نقر عليه المُستخدِم لغلق الصندوق، ويكون صندوق النافذة من النوع <code>Dialog&lt;ButtonType&gt;‎</code> في تلك الحالة.
</p>

<p>
	يُعدّ <code>Alert</code> صنفًا فرعيًا من الصنف <code>Dialog&lt;ButtonType&gt;‎</code>، إذ يُسهِّل إنشاء صناديق النافذة التقليدية التي تَعرِض رسالةً نصيةً للمُستخدِم مصحوبةً بزرٍ واحدٍ أو اثنين، وتَعمَل بمثابة تنبيهٍ للمُستخدِم. كنا قد استخدمنا هذا الصنف فعليًا في مقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/" rel="">مدخل إلى التعامل مع الملفات في جافا</a> لكي نَعرِض بعض رسائل الخطأ للمُستخدِم، ولكن دون أن نتعرَّض لطريقة عمله.
</p>

<p>
	يُمكِننا إنشاء كائنٍ من النوع <code>Alert</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_9" style="">
<span class="typ">Alert</span><span class="pln"> alert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="pln"> alertType</span><span class="pun">,</span><span class="pln"> message </span><span class="pun">);</span></pre>

<p>
	ينتمي المعامل الأول إلى نوع التعداد <code>Alert.AlertType</code> الذي يُمكِن لقيمته أن تكون واحدةً مما يلي:
</p>

<ul>
<li>
		<code>Alert.AlertType.INFORMATION</code>.
	</li>
	<li>
		<code>Alert.AlertType.WARNING</code>.
	</li>
	<li>
		<code>Alert.AlertType.ERROR</code>.
	</li>
	<li>
		<code>Alert.AlertType.CONFIRMATION</code>.
	</li>
</ul>
<p>
	وعند تخصيص أي من القيم الثلاثة الأولى، سيحتوي الصندوق المعروض على زر "OK" وحيد ولا يفعل أكثر من مجرد عرض رسالةٍ نصيةٍ للمُستخدِم، وفي تلك الحالة، لا حاجة لفحص أو استرجاع القيمة المعادة من التابع <code>alert.showAndWait()‎</code>.
</p>

<p>
	عند تمرير القيمة الأخيرة، سيتضمَّن الصندوق زر "OK" و زر "Cancel"، ويُستخدَم عادةً لسؤال المُستخدِم عما إذا كان يريد الاستمرار بتنفيذ عملية يُحتمَل أن تكون خطيرةً، مثل حذف ملف؛ إذ يكون من الضروري فحص القيمة المعادة من التابع في تلك الحالة، وهذا هو ما تفعله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_11" style="">
<span class="typ">Alert</span><span class="pln"> confirm </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">CONFIRMATION</span><span class="pun">,</span><span class="pln">
                         </span><span class="str">"Do you really want to delete "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="typ">Optional</span><span class="pun">&lt;</span><span class="typ">ButtonType</span><span class="pun">&gt;</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> confirm</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">isPresent</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ButtonType</span><span class="pun">.</span><span class="pln">OK </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    file</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إضافةً إلى الأزرار، قد يحتوي صندوق النافذة على مساحةٍ للمحتوى وعنوانٍ رئيسي يَظهَر أعلى تلك المساحة، ورسمةٍ تَظهَر إلى جانب العنوان الرئيسي إن وجد، أو إلى جانب المحتوى؛ وبالتأكيد عنوان يَظهَر بشريط عنوان صندوق النافذة. تكون الرسمة عادةً أيقونةً صغيرةً؛ فبالنسبة لصناديق النافذة من النوع <code>Alert</code>، تُوضَع الرسالة بمساحة المحتوى، وتُضبَط الخاصيات الأخرى أوتوماتيكيًا بما يتناسب مع نوع التنبيه المُستخدَم، ويُمكِن مع ذلك تعديلها باستدعاء التوابع التالية المُعرَّفة بالصنف <code>Dialog</code> قبل عرض التنبيه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_13" style="">
<span class="pln">alert</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="pln"> windowTitle </span><span class="pun">);</span><span class="pln">
alert</span><span class="pun">.</span><span class="pln">setGraphic</span><span class="pun">(</span><span class="pln"> node </span><span class="pun">);</span><span class="pln">
alert</span><span class="pun">.</span><span class="pln">setHeaderText</span><span class="pun">(</span><span class="pln"> headerText </span><span class="pun">);</span></pre>

<p>
	يُمكِن لأي من تلك القيم المُمرَّرة أن تكون قيمةً فارغةً <code>null</code>، كما يُمكِننا ضبط المحتوى إلى أي عقدة مبيان مشهد عشوائية لتحلّ محل الرسالة النصية، وذلك باستدعاء التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_15" style="">
<span class="pln">alert</span><span class="pun">.</span><span class="pln">getDialogPane</span><span class="pun">().</span><span class="pln">setContent</span><span class="pun">(</span><span class="pln"> node </span><span class="pun">);</span></pre>

<p>
	ولكننا نُطبِّق ذلك عادةً على صندوق نافذة عادي من النوع <code>Dialog</code>، لا على تنبيهٍ من النوع <code>Alert</code>. تُوضِّح تنبيهات التأكيد بالصورة التالية المكونات المختلفة الموجودة بأي صندوق نافذة، إذ يُمكِّننا ملاحِظة أن العنوان الرئيسي الخاص بالصندوق الموجود على يمين الصورة فارغ؛ وإذا أردت عَرض نصٍ متعدد الأسطر ضمن تنبيه، فلا بُدّ من إضافة محرف السطر الجديد ("‎\n") إلى النص:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="96829" data-unique="725q81opy" src="https://academy.hsoub.com/uploads/monthly_2022_04/001Alerts.png.9b3241b6082a4b73942098b045658f65.png" style="width: 550px; height: auto;" alt="001Alerts.png"></p>

<p>
	تُوفِّر<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B9%D8%B6-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r1146/" rel="">مكتبة JavaFX</a> الصنف الفرعي <code>TextInputDialog</code> المُشتَق من الصنف <code>Dialog&lt;String&gt;‎</code> لتمثيل صناديق النافذة التي تقرأ مُدْخَلًا من المُستخدِم. ويعيد التابع <code>showAndWait()‎</code> في تلك الحالة قيمةً من النوع <code>Optional&lt;String&gt;‎</code>، كما يحتوي صندوق النافذة المُستخدِم لذلك الصنف على حقلٍ نصي من النوع <code>TextField</code>، مما يُمكِّن المُستخدِم من إدخال سطر نصي؛ كذلك، يحتوي على زر "OK" و زر "Cancel". يَستقبِل الباني معاملًا من النوع <code>String</code>، والذي يُمثِّل المحتوى المبدئي للحقل النصي؛ فإذا أردنا أن نطرح سؤالًا على المُستخدِم أو أن نَعرِض رسالةً معينةً عليه، فيُمكِننا وضعها بالعنوان الرئيسي للصندوق.
</p>

<p>
	يعيد صندوق النافذة محتويات الحقل النصي إن وُجدت، والتي قد تكون مجرد <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/?msclkid=06cd8fe0c13511ec88de2d1ff3c825ad" rel="">سلسلةٍ نصيةٍ</a> فارغة، وإذا نقر المُستخدِم على زر "Cancel" أو أغلق صندوق النافذة ببساطة، فستكون القيمة المعادة غير موجودة. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_17" style="">
<span class="typ">TextInputDialog</span><span class="pln"> getNameDialog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TextInputBox</span><span class="pun">(</span><span class="str">"Fred"</span><span class="pun">);</span><span class="pln">
getNameDialog</span><span class="pun">.</span><span class="pln">setHeaderText</span><span class="pun">(</span><span class="str">"Please enter your name."</span><span class="pun">);</span><span class="pln">
</span><span class="typ">Optional</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">&gt;</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> getNameDialog</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">isPresent</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">get</span><span class="pun">().</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">length</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">get</span><span class="pun">().</span><span class="pln">trim</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Alert</span><span class="pln"> error </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                               </span><span class="str">"Anonymous users are not allowed!"</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    error</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
    </span><span class="typ">System</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">):</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إلى جانب الصنفين <code>Alert</code> و <code>TextInputDialog</code>، يُعرِّف الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/SimpleDialogs.java" rel="external nofollow">SimpleDialogs.java</a> -كتبه المؤلف- التوابع الساكنة static التالية التي يُمكِن استخدامها لعرض أكثر أنواع صناديق النافذة شيوعًا:
</p>

<ul>
<li>
		<code>SimpleDialogs.message(text)‎</code>: يعرِض صندوق نافذة يحتوي على رسالة نصية وزر"OK"، ولا يًعيد أي قيمة. لاحِظ أنك لا تحتاج إلى كتابة محرف السطر الجديد ضمن الرسالة إذا أردتها أن تكون متعددة الأسطر؛ لأن ذلك يحدث تلقائيًا مع الرسائل الطويلة. يَستقبِل التابع أيضًا معاملًا ثانيًا اختياريًا لتخصيص عنوان صندوق النافذة.
	</li>
	<li>
		<code>SimpleDialogs.prompt(text)‎</code>: يعرِض صندوق نافذة يحتوي على رسالة نصية وحقل إدخال نصي مع زر "OK" و زر "Cancel"، ويُعيد قيمةً من النوع <code>String</code> تُمثِّل محتويات الحقل النصي إذا كان المُستخدِم قد نقر على "OK"؛ أو على القيمة الفارغة <code>null</code> إذا كان المُستخدِم قد أغلق الصندوق. يَستقبِل التابع أيضًا معاملين اختياريين آخرين لتخصيص عنوان صندوق النافذة، والمحتوى المبدئي للحقل النصي على الترتيب.
	</li>
	<li>
		<code>SimpleDialogs.confirm(text)‎</code>: يَعرِض صندوق نافذة يحتوي على رسالة نصية مع زر "Yes" و زر "No" و زر "Cancel"، ويُعيد قيمةً من النوع <code>String</code>، والتي لا بُدّ أن تكون واحدةً من القيم التالية "yes" أو "no" أو "cancel". يَستقبِل التابع معاملًا ثانيًا اختياريًا لتخصيص عنوان صندوق النافذة مثل التابعين السابقين.
	</li>
</ul>
<p>
	يحتوي الصنف <code>SimpleDialogs</code> على بعض الخيارات الأخرى، مثل صندوق نافذة بسيط لاختيار لون، والتي يُمكِنك الإطلاع عليها بقراءة شيفرة الملف <a href="https://math.hws.edu/javanotes/source/chapter13/SimpleDialogs.java" rel="external nofollow">SimpleDialogs.java</a>؛ كما يَسمَح أيضًا البرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/TestDialogs.java" rel="external nofollow">TestDialogs.java</a> للمُستخدِم بتجربة صناديق النافذة المختلفة المُعرَّفة بالصنف <code>SimpleDialogs</code>.
</p>

<h2>
	الصنفان WebView و WebEngine
</h2>

<p>
	سنناقش ببقية هذا المقال برنامج متصفح إنترنت مُتعدّد النوافذ، إذ تبدو كتابة متصفح إنترنت عمليةً معقدة، وهي كذلك فعلًا، ولكن مكتبة JavaFX تُسهِّل ذلك كثيرًا بتنفيذ غالبية العمل المطلوب ضمن مجموعةٍ من الأصناف القياسية؛ إذ يُمثِّل الصنف <code>WebView</code> المُعرَّف بحزمة <code>javafx.scene.control</code> أداة تحكُّم يُمكِنها تحميل صفحة إنترنت وعرضها، كما تستطيع تلك الأداة معالجة معظم صفحات الإنترنت جيدًا بما في ذلك تنفيذ شيفرة JavaScript، إذ تُستخدَم لغة البرمجة <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافاسكربت JavaScript</a> لبرمجة صفحات إنترنت ديناميكية، ولا علاقة لها <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/?msclkid=6bf70240c13511ec956ae171177ab97d" rel="">بلغة Java</a>).
</p>

<p>
	إضافةً لما سبق، يُمثِّل الصنف <code>WebView</code> العرض view ضمن نمط نموذج-عرض-مُتحكَّم Model-View-Controller الذي ناقشناه بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%86-%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D8%A7%D8%AE%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%AC%D8%A7%D9%81%D8%A7-r1526/" rel="">أمثلة عن رسوميات فاخرة باستعمال جافا</a>؛ إذ يُنفَّذ غالبية العمل المطلوب لتحميل صفحات الإنترنت وإدارتها من خلال كائنٍ من النوع <code>WebEngine</code>، والذي يُمثِّل جزءًا من المُتحكِّم controller؛ أما النموذج، فهو هيكل بياني data structure يتضمَّن محتويات صفحة الإنترنت. ويُنشِئ الصنف <code>WebEngine</code> النموذج عند تحميل الصفحة، ثم يَعرِض الصنف <code>WebView</code> محتوياتها.
</p>

<p>
	يجب أن نَعرِض كائن الصنف <code>WebView</code> ضمن نافذة؛ إذ يُمثِّل الصنف الفرعي <a href="https://math.hws.edu/javanotes/source/chapter13/BrowserWindow.java" rel="external nofollow">BrowserWindow.java</a> -المُشتَق من صنف النافذة القياسي <code>Stage</code>- نافذة متصفح إنترنت كاملة، وهو يحتوي على كائنٍ ينتمي إلى الصنف <code>WebView</code>، بالإضافة إلى شريط قوائم وبعض أدوات التحكُّم الأخرى، مثل صندوق إدخال نصي يُمكِّن المُستخدِم من كتابة محدّد موارد مُوحد URL لصفحة إنترنت معينة، وزر "Load" ينقر عليه المُستخدِم لتحميل صفحة الإنترنت من محدّد الموارد الموحد إلى كائن الصنف <code>WebView</code>. بالإضافة إلى ذلك، يُمكِن لباني الصنف <code>BrowserWindow</code> أن يَستقبِل معاملًا إضافيًا لتخصيص محدِّد موارد موحد مبدئي يُحمَّل تلقائيًا عند فتح النافذة.
</p>

<p>
	يتضمَّن كل كائن من النوع <code>WebView</code> كائنًا آخرًا من النوع <code>WebEngine</code>، والذي يُمكِننا استرجاعه باستدعاء التابع <code>webEngine = webview.getEngine()‎</code>. يمكننا الآن تحميل load صفحة إنترنت باستدعاء ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_19" style="">
<span class="pln">webEngine</span><span class="pun">.</span><span class="pln">load</span><span class="pun">(</span><span class="pln"> urlString </span><span class="pun">);</span></pre>

<p>
	إذ أن <code>urlString</code> سلسلةٌ نصيةٌ تُمثِّل محدِّد موارد موحد -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>-. ويجب أن تبدأ تلك السلسلة ببروتوكول، مثل "http:‎" أو "https:‎"، ولهذا يضيف البرنامج كلمة "http://‎" إلى مقدمة السلسلة النصية المُتضمِّنة لمُحدّد الموارد الموحد، إذا لم تكن تحتوي على بروتوكول فعليًا.
</p>

<p>
	إضافةً لما سبق، يُحمِّل البرنامج صفحة إنترنت جديدة أوتوماتيكيًا، وذلك إذا نقر المُستخدِم على رابط ضمن الصفحة المعروضة حاليًا.
</p>

<p>
	تُحمَّل صفحات الإنترنت بصورةٍ غير متزامنة asynchronous؛ أي أن التابع <code>webEngine.load()‎</code> يعود فورًا، في حين تُحمَّل صفحة الإنترنت ضمن <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/?msclkid=d5ca34fbc13511ecb3d06b681db7026f" rel="">خيط thread</a> مُشغَّلٍ بالخلفية. وعند اكتمال عملية التحميل، تُعرَض صفحة الإنترنت داخل كائن الصنف <code>WebView</code>؛ أما في حالة فشل التحميل لسببٍ ما، فلا يحدث أي تنبيه تلقائي، ولكن ما يزال بإمكاننا الحصول على بعض المعلومات بإضافة <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r690" rel="">مستمعي أحداث</a> إلى الخاصيتين <code>location</code> و <code>title</code> -من النوع <code>String</code>- القابلتين للمراقبة observable، والمُعرَّفتين بالصنف <code>WebEngine</code>؛ إذ تُمثِّل الخاصية <code>location</code> محدد الموارد الموحد لصفحة الإنترنت المعروضة حاليًا، أو التي يُجرَى تحميلها؛ بينما تُمثِّل الخاصية <code>title</code> عنوان صفحة الإنترنت الحالية، والتي تَظهَر بشريط عنوان النافذة التي تَعرِض صفحة الإنترنت. على سبيل المثال، يستمع الصنف <code>BrowserWindow</code> إلى الخاصية <code>title</code> ويَضبُط عنوان النافذة ليتوافق مع محتوياتها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_21" style="">
<span class="pln">webEngine</span><span class="pun">.</span><span class="pln">titleProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="pln">o</span><span class="pun">,</span><span class="pln">oldVal</span><span class="pun">,</span><span class="pln">newVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newVal </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
        setTitle</span><span class="pun">(</span><span class="str">"Untitled "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> owner</span><span class="pun">.</span><span class="pln">getNextUntitledCount</span><span class="pun">());</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        setTitle</span><span class="pun">(</span><span class="pln">newVal</span><span class="pun">);</span><span class="pln"> 
</span><span class="pun">});</span></pre>

<p>
	يستمع البرنامج إلى الخاصية <code>location</code> أيضًا، ويَعرِض قيمتها داخل عنوان من النوع <code>Label</code> أسفل النافذة؛ في حين سنناقش <code>owner</code> لاحقًا بالأسفل.
</p>

<p>
	يُمكِننا أيضًا أن نضيف مستمعًا إلى خاصية <code>webEngine.getLoadWorker().stateProperty()‎</code> لمراقبة تقدّم تحميل الصفحة. ألقِ نظرةً على شيفرة الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/BrowserWindow.java" rel="external nofollow">BrowserWindow.java</a> لترى مثالًا على ذلك.
</p>

<p>
	ذكرنا بالأعلى أن كائن الصنف <code>WebView</code> (مع كائن الصنف <code>WebEngine</code> الخاص به) قادرٌ على تشغيل شيفرة JavaScript المُضمَّنة بصفحات الإنترنت؛ ولكن هذا ليس دقيقًا تمامًا، إذ تحتوي JavaScript على بعض البرامج الفرعية subroutines المسؤولة عن إظهار صناديق نوافذ بسيطة؛ فعلى سبيل المثال، يَعرِض صندوق النافذة من النوع "alert" رسالةً نصيةً للمُستخدِم؛ بينما يطرح صندوق النافذة من النوع "prompt" سؤالًا على المُستخدِم ويتلقى ردَّه النصي عليها؛ أما صندوق النافذة من النوع "confirm"، فيَعرِض رسالةً للمُستخدِم مع زر "OK" و زر "Cancel"، ويتلقى قيمةً مُعادةً من النوع <code>boolean</code> تشير إلى ما إذا كان المُستخدِم قد أغلق الصندوق بالنقر على زر "OK".
</p>

<p>
	في الواقع، يتجاهل الصنف <code>WebEngine</code> طلبات JavaScript لعرض تلك الصناديق افتراضيًا، ولكن يُمكِننا إضافة مستمعي أحداث للاستجابة إلى تلك الطلبات، إذ يَستخدِم الصنف <code>BrowserWindow</code> صناديق نافذة من تلك المُعرَّفة بالصنف <code>SimpleDialogs</code> للرد على تلك الأحداث؛ فعندما تحاول JavaScript أن تَعرِض صندوق نافذة تنبيهي مثلًا، فسيُولِّد كائن الصنف <code>WebEngine</code> حدثًا من النوع <code>AlertEvent</code>، يتضمَّن الرسالة التي تريد <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> أن تَعرِضها، وسيستجيب الصنف <code>BrowserWindow</code> باستدعاء <code>SimpleDialogs.message()‎</code> لكي يَعرِض الرسالة للمُستخدِم. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_23" style="">
<span class="pln">webEngine</span><span class="pun">.</span><span class="pln">setOnAlert</span><span class="pun">(</span><span class="pln"> 
        evt </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="typ">SimpleDialogs</span><span class="pun">.</span><span class="pln">message</span><span class="pun">(</span><span class="pln">evt</span><span class="pun">.</span><span class="pln">getData</span><span class="pun">(),</span><span class="pln"> </span><span class="str">"Alert from web page"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	تختلف معالجة صناديق النافذة من النوعين prompt و confirm بعض الشيء؛ لكونهما يعيدان قيمةً، وتبين الشيفرة التالية الطريقة التي اتبعها البرنامج لمعالجتهما:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_25" style="">
<span class="pln">webEngine</span><span class="pun">.</span><span class="pln">setPromptHandler</span><span class="pun">(</span><span class="pln"> promptData </span><span class="pun">-&gt;</span><span class="pln"> 
            </span><span class="typ">SimpleDialogs</span><span class="pun">.</span><span class="pln">prompt</span><span class="pun">(</span><span class="pln"> promptData</span><span class="pun">.</span><span class="pln">getMessage</span><span class="pun">(),</span><span class="pln"> 
                      </span><span class="str">"Query from web page"</span><span class="pun">,</span><span class="pln"> promptData</span><span class="pun">.</span><span class="pln">getDefaultValue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
webEngine</span><span class="pun">.</span><span class="pln">setConfirmHandler</span><span class="pun">(</span><span class="pln"> str </span><span class="pun">-&gt;</span><span class="pln"> 
            </span><span class="typ">SimpleDialogs</span><span class="pun">.</span><span class="pln">confirm</span><span class="pun">(</span><span class="pln">str</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Confirmation Needed"</span><span class="pun">).</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"yes"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	لم نناقش بعد شريط القوائم الذي يدعمه الصنف <code>BrowserWindow</code>؛ إذ يحتوي ذلك الشريط على قائمةٍ واحدةٍ اسمها "Window"، وتحتوي بدورها على مجموعةٍ من الأوامر لأغراضٍ معينة، مثل فتح نافذة متصفح جديدة أو غلْق النافذة الحالية، كما تحتوي على قائمةٍ مكوّنة من نوافذ المتصفح المفتوحة حاليًا، ويستطيع المُستخدِم أن يختار أيًا منها ليُحضره إلى مقدمة الشاشة؛ ولكي تفهم طريقة تنفيذ ذلك، عليك أولًا أن تفهم طريقة استخدام الصنف <code>BrowserWindow</code> ضمن برنامجٍ متُعدّد النوافذ.
</p>

<h2>
	إدارة عدة نوافذ
</h2>

<p>
	لا يُعدّ الصنف <code>BrowserWindow</code> تطبيقًا، أي لا يُمكِن تشغيله على أنه برنامجٌ بحد ذاته، وإنما يُمثِّل نافذةً واحدةً ضمن برنامجٍ متعدد النوافذ. ستجِد النسخة القابلة للتشغيل من هذا الصنف بالملف <a href="https://math.hws.edu/javanotes/source/chapter13/WebBrowser.java" rel="external nofollow">WebBrowser.java</a>. يمتد الصنف <code>WebBrowser</code> من الصنف <code>Application</code> مثل أي برنامجٍ مُصمَّم بمكتبة JavaFX، كما يعتمد على الصنفين <a href="https://math.hws.edu/javanotes/source/chapter13/BrowserWindow.java" rel="external nofollow">BrowserWindow.java</a> و <a href="https://math.hws.edu/javanotes/source/chapter13/SimpleDialogs.java" rel="external nofollow">SimpleDialogs.java</a>؛ ولذلك ستحتاج إلى الأصناف الثلاثة لكي تتمكَّن من تشغيل البرنامج.
</p>

<p>
	يحتوي أي صنفٍ ينتمي للنوع <code>Application</code> على التابع <code>start()‎</code> الذي يستدعيه النظام عند بدء تشغيل التطبيق، إذ يستقبل هذا التابع معاملًا من النوع <code>Stage</code> يُخصِّص النافذة الرئيسية للبرنامج، ولكن ليس من الضروري للبرنامج أن يَستخدِم تلك النافذة فعليًا؛ إذ يتجاهل التابع <code>start()‎</code> المُعرَّف بالصنف <code>WebBrowser</code> النافذة الرئيسية المُخصَّصة، ويُنشِئ بدلًا منها نافذةً من النوع <code>BrowserWindow</code>، لتكون هي أول ما يَظهَر عند تشغيل البرنامج، وقد ضُبطَت تلك النافذة لتُحمِّل الصفحة الرئيسية لصفحة الإنترنت التي تحتوي على النسخة المُتاحة عبر الإنترنت من هذا الكتاب.
</p>

<p>
	يُمثِّل ما سبق كل ما ينبغي أن يفعله الصنف <code>WebBrowser.java</code> لتنفيذ البرنامج باستثناء القائمة "Window" التي تحتوي على قائمةٍ بكل النوافذ المفتوحة. ونظرًا لعدم كون تلك القائمة جزءًا من بيانات أي نافذة على حدة، فقد كان من الضروري الاحتفاظ بها بمكانٍ آخر، مثل كائن الصنف <code>WebBrowser</code>، ويمكن بدلًا من ذلك تخزين قائمة النوافذ مثل متغير عضو ساكن static بالصنف <code>BrowserWindow</code>، مما يَعنِي تشاركُه بين جميع النسخ المنشأة من ذلك الصنف؛ إذ يُعرِّف الصنف <code>WebBrowser</code> تابعه <code>newBrowserWindow()‎</code> بغرض فتح نافذةٍ جديدة؛ بينما يحتوي الصنف <code>BrowserWindow</code> على متغير النسخة <code>owner</code> للإشارة إلى كائن الصنف <code>WebBrowser</code> الذي فتح النافذة. وبالتالي إذا أراد البرنامج فتح نافذة جديدة، فإنه يفعل ذلك باستدعاء التابع <code>owner.newBrowserWindow(url)‎</code>، إذ يشير المعامل <code>url</code> إلى مُحدِّد الموارد الموحد الخاص بصفحة الإنترنت المطلوب تحميلها بالنافذة الجديدة. وفي المقابل، قد يحتوي المعامل على القيمة الفارغة <code>null</code> بهدف فتح نافذة متصفحٍ فارغة.
</p>

<p>
	يتحدََد حجم أي نافذة بمكتبة JavaFX وفقًا لحجم المشهد -من النوع <code>Scene</code>- المعروض داخلها افتراضيًا، كما تظهر النافذة بمنتصف الشاشة افتراضيًا؛ ويُمكِننا بدلًا من ذلك ضبط حجم النافذة ومكانها قبل فتحها. بالنسبة للبرامج متعددة النوافذ، لا يُحبَّذ عرض جميع النوافذ بنفس المكان بالضبط، كما يبدو أن الحجم الافتراضي لكائنات الصنف <code>BrowserWindow</code> صغيرٌ جدًا بمعظم شاشات الحاسوب؛ ولذلك يَضبُط التطبيق WebBrowser موضع جميع النوافذ التي يفتحها ليَبعُد مكان كل نافذةٍ مسافةً قصيرةً عن مكان النافذة التي فتحها التطبيق بالمرة السابقة؛ كما يضبُط التطبيق حجم النافذة بما يتناسب مع حجم الشاشة.
</p>

<p>
	يحتوي الصنف <code>Screen</code> المُعرَّف بحزمة <code>javafx.stage</code> على التابع الساكن <code>Screen.getPrimary()‎</code> الذي يعيد كائنًا يتضمَّن معلوماتٍ عن الشاشة الرئيسية للحاسوب؛ كما ويحتوي ذلك الكائن بدوره على كل من التابعين <code>Screen.getPrimary().getVisualBounds()‎</code>، الذي يُعيد كائنًا من النوع <code>Rectangle2D</code> ويُمثِّل المساحة القابلة للاستخدام من الشاشة الرئيسية. يَستدعِي تابع البرنامج <code>start()‎</code> ذلك التابع لكي يَحسِب كُلًا من حجم أول نافذة ومكانها على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_28" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> start</span><span class="pun">(</span><span class="typ">Stage</span><span class="pln"> stage</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// (stage is not used)</span><span class="pln">

    openWindows </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">BrowserWindow</span><span class="pun">&gt;();</span><span class="pln">  </span><span class="com">// List of open windows.</span><span class="pln">

    screenRect </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Screen</span><span class="pun">.</span><span class="pln">getPrimary</span><span class="pun">().</span><span class="pln">getVisualBounds</span><span class="pun">();</span><span class="pln">

       </span><span class="com">// 1</span><span class="pln">
    locationX </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMinX</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">
    locationY </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMinY</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">

       </span><span class="com">// 2</span><span class="pln">
    windowHeight </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">160</span><span class="pun">;</span><span class="pln">
    windowWidth </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">130</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">windowWidth </span><span class="pun">&gt;</span><span class="pln"> windowHeight</span><span class="pun">*</span><span class="lit">1.6</span><span class="pun">)</span><span class="pln">
        windowWidth </span><span class="pun">=</span><span class="pln"> windowHeight</span><span class="pun">*</span><span class="lit">1.6</span><span class="pun">;</span><span class="pln">

       </span><span class="com">// 3</span><span class="pln">
    newBrowserWindow</span><span class="pun">(</span><span class="str">"http://math.hws.edu/javanotes/index.html"</span><span class="pun">);</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end start()</span></pre>

<p>
	حيث أن: 
</p>

<ul>
<li>
		[1]: يشير (locationX,locationY) إلى مكان الركن الأيسر العلوي للنافذة التي ستُفتَح بالمرة القادمة، وتتحرك النافذة الأولى إلى الأسفل قليلًا من الركن الأيسر العلوي للجوانب المرئية للشاشة الرئيسية.
	</li>
	<li>
		[2]: تعني أن حجم النافذة يعتمد على طول وعرض جوانب الشاشة المرئية بما يَسمَح ببعض المسافات الإضافية حتى يكون من الممكن وضع النوافذ فوق بعضها، مع إزاحة كل واحدة عن سابقتها قليلًا. قيِّد العرض ليكون على الأكثر 1.6 مرة من الطول لأسباب جمالية.
	</li>
	<li>
		[3]: تعني افتح النافذة الأولى لتَعرِض الصفحة الأمامية للكتاب.
	</li>
</ul>
<p>
	عندما يَفتح التابع <code>newBrowserWindow()‎</code> نافذةً جديدةً، سيعتمد حجمها ومكانها على قيم المتغيرات <code>windowWidth</code> و <code>windowHeight</code> و <code>locationX</code> و <code>locationY</code>؛ ولهذا ينبغي أن نُعدِّل قيم المتغيرين <code>locationX</code> و <code>locationY</code> لكي تَظهَر النافذة التالية بمكانٍ مختلف؛ وعلينا أيضًا أن نضيف النافذة الجديدة إلى قائمة النوافذ المفتوحة؛ كما ينبغي أن نتأكَّد من حذف النافذة من القائمة عند غلقها. لحسن الحظ، تُولِّد أي نافذة حدثًا عند غلقها، وبالتالي يُمكِننا أن نُضيف مستمعًا إلى ذلك الحدث، ليَحذِف معالج الحدث النافذة من قائمة النوافذ المفتوحة.
</p>

<p>
	ألقِ نظرةً على شيفرة التابع <code>newBrowserWindow()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_30" style="">
<span class="kwd">void</span><span class="pln"> newBrowserWindow</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> url</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">BrowserWindow</span><span class="pln"> window </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BrowserWindow</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
    openWindows</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln"> </span><span class="com">// Add new window to open window list.</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">setOnHidden</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// 1</span><span class="pln">
        openWindows</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln"> window </span><span class="pun">);</span><span class="pln">
        </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Number of open windows is "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> openWindows</span><span class="pun">.</span><span class="pln">size</span><span class="pun">());</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">openWindows</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// 2</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Program ends because all windows are closed"</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">url </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        window</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"Untitled "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> getNextUntitledCount</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">setX</span><span class="pun">(</span><span class="pln">locationX</span><span class="pun">);</span><span class="pln">         </span><span class="com">// set location and size of the window</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">setY</span><span class="pun">(</span><span class="pln">locationY</span><span class="pun">);</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">setWidth</span><span class="pun">(</span><span class="pln">windowWidth</span><span class="pun">);</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">setHeight</span><span class="pun">(</span><span class="pln">windowHeight</span><span class="pun">);</span><span class="pln">
    window</span><span class="pun">.</span><span class="pln">show</span><span class="pun">();</span><span class="pln">
    locationX </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">    </span><span class="com">// set up location for NEXT window</span><span class="pln">
    locationY </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">locationX </span><span class="pun">+</span><span class="pln"> windowWidth </span><span class="pun">+</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMaxX</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// 3</span><span class="pln">
        locationX </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMinX</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">locationY </span><span class="pun">+</span><span class="pln"> windowHeight </span><span class="pun">+</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMaxY</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// 4</span><span class="pln">
        locationY </span><span class="pun">=</span><span class="pln"> screenRect</span><span class="pun">.</span><span class="pln">getMinY</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وتعني كل من:
</p>

<ul>
<li>
		[1]: يُستدعَى عند غلق النافذة، وذلك ليحذف النافذة من قائمة النوافذ المفتوحة.
	</li>
	<li>
		[2]: ينتهي البرنامج أوتوماتيكيًا عند غلق جميع النوافذ.
	</li>
	<li>
		[3]: إذا كانت النافذة ستمتد إلى ما بعد طرف الشاشة الأيمن، فأعِد ضبط المتغير <code>locationX</code> إلى قيمته الأصلية.
	</li>
	<li>
		[4]: إذا كانت النافذة ستمتد إلى ما بعد طرف الشاشة السفلي، فأعِد ضبط المتغير <code>locationY</code> إلى قيمته الأصلية.
	</li>
</ul>
<p>
	يتضمَّن الصنف <code>WebBrowser</code> التابع <code>getOpenWindowList()‎</code>، والذي يعيد قائمةً بكل النوافذ المفتوحة؛ إذ يستدعِي كائن الصنف <code>BrowserWindow</code> ذلك التابع أثناء إنشائه للقائمة "Window". وفي الواقع، لا يحدث ذلك بأفضل كفاءةٍ ممكنة، ويُعاد بناء القائمة بكل مرة تُعرَض خلالها؛ إذ تُولِّد القائمة حدثًا عندما ينقُر المُستخدِم على اسم القائمة، وأيضًا قبل ظهورها مباشرةً. يُسجِّل الصنف <code>BrowserWindow</code> مستمعًا إلى ذلك الحدث، ويسترجِع معالج هذا الحدث قائمة النوافذ المفتوحة باستدعاء التابع <code>owner.getOpenWindowList()‎</code>، ويَستخدِمها لإعادة بناء القائمة قبل أن يُظهِرها على الشاشة. ألقِ نظرةً على شيفرة التابع المُعرَّف بالصنف <code>BrowserWindow</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4793_32" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> populateWindowMenu</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">BrowserWindow</span><span class="pun">&gt;</span><span class="pln"> windows </span><span class="pun">=</span><span class="pln"> owner</span><span class="pun">.</span><span class="pln">getOpenWindowList</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// 1</span><span class="pln">
        windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">windows</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// 2</span><span class="pln">
        </span><span class="typ">MenuItem</span><span class="pln"> item </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">MenuItem</span><span class="pun">(</span><span class="str">"Close All and Exit"</span><span class="pun">);</span><span class="pln">
        item</span><span class="pun">.</span><span class="pln">setOnAction</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">item</span><span class="pun">);</span><span class="pln">
        windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SeparatorMenuItem</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">BrowserWindow</span><span class="pln"> window </span><span class="pun">:</span><span class="pln"> windows</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">String</span><span class="pln"> title </span><span class="pun">=</span><span class="pln"> window</span><span class="pun">.</span><span class="pln">getTitle</span><span class="pun">();</span><span class="pln"> </span><span class="com">// Menu item text is the window title.</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title</span><span class="pun">.</span><span class="pln">length</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">60</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="com">// 3</span><span class="pln">
            title </span><span class="pun">=</span><span class="pln"> title</span><span class="pun">.</span><span class="pln">substring</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="lit">57</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">". . ."</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="typ">MenuItem</span><span class="pln"> item </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">MenuItem</span><span class="pun">(</span><span class="pln">title</span><span class="pun">);</span><span class="pln">
        final </span><span class="typ">BrowserWindow</span><span class="pln"> win </span><span class="pun">=</span><span class="pln"> window</span><span class="pun">;</span><span class="pln"> </span><span class="com">// (for use in a lambda expression)</span><span class="pln">
            </span><span class="com">// 4</span><span class="pln">
        item</span><span class="pun">.</span><span class="pln">setOnAction</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> win</span><span class="pun">.</span><span class="pln">requestFocus</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        windowMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">item</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">window </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// 5</span><span class="pln">
            item</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث تعني كل من:
</p>

<ul>
<li>
		[1]: تتكون القائمة من 4 عناصر دائمة. اِحذِف العناصر الأخرى المقابلة للنوافذ المفتوحة التي تُركت من آخر مرة عُرِضَت خلالها القائمة.
	</li>
	<li>
		[2]: أضف الأمر "Close All" فقط إذا لم تكن تلك هي النافذة الوحيدة.
	</li>
	<li>
		[3]: لا تَستخدِم نصوصًا طويلةً جدًا لعناصر القائمة.
	</li>
	<li>
		[4]: سيُحضِر معالج الحدث لعنصر القائمة ذاك النافذة المقابلة إلى المقدمة باستدعاء التابع <code>requestFocus()‎</code> الخاص بها.
	</li>
	<li>
		[5]: نظرًا لأن النافذة موجودة بالمقدمة فعليًا، فعطِّل العنصر المقابل لتلك النافذة.
	</li>
</ul>
<p>
	كما ترى، ليس من الصعب إدارة تطبيق متعدد النوافذ، كما أنه من السهل كتابة متصفح إنترنت بوظائف معقولة نوعًا ما باستخدام مكتبة JavaFX. كان هذا مثالًا جيدًا على استخدام أصناف موجودة مثل قاعدة نبني عليها أصنافًا أخرى. رأينا أيضًا أمثلةً جديدةً جيدةً للتعامل مع الأحداث، وبذلك نكون قد وصلنا تقريبًا إلى نهاية هذه السلسلة. سنناقش في المقال الأخير بعض الأشياء المتعلقة ببرمجة واجهات المُستخدِم الرسومية.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="https://math.hws.edu/javanotes/c13/s4.html" rel="external nofollow">Section 4: Mostly Windows and Dialogs</a> من فصل Chapter 13: GUI Programming Continued من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%A8%D8%A9-%D9%88%D9%86%D9%85%D8%B7-mvc-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1527/" rel="">مكونات الواجهة المركبة ونمط MVC في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r967/?msclkid=9e2117edc13611ec871c447a932d4a7b" rel="">واجهة المستخدم الحديثة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/?msclkid=9e213e1dc13611ec96c4c30c732655f6" rel="">مدخل إلى التعامل مع الملفات في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1538</guid><pubDate>Fri, 15 Apr 2022 15:00:00 +0000</pubDate></item><item><title>&#x645;&#x643;&#x648;&#x646;&#x627;&#x62A; &#x627;&#x644;&#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x645;&#x631;&#x643;&#x628;&#x629; &#x648;&#x646;&#x645;&#x637; MVC &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%A8%D8%A9-%D9%88%D9%86%D9%85%D8%B7-mvc-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1527/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/62501bf4c6476_----MVC.png.3e640221d0d3a0621a07b207f4696575.png" /></p>

<p>
	تتضمَّن واجهة تطوير التطبيقات JavaFX تعقيداتٍ أكبر بكثير مما درسناه إلى الآن، ولكن كل هذا التعقيد يعمل لصالح المبرمج عمومًا؛ فهو على الأغلب يكون مخفيًا بالاستخدامات الأكثر شيوعًا لمكتبة JavaFX، أي أنك لست مضطّرًا إلى معرفة تفاصيل أدوات التحكُّم الأكثر تعقيدًا لكي تتمكَّن من استخدامها بفعالية بغالبية البرامج.
</p>

<p>
	تُعرِّف <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150" rel="">مكتبة JavaFX</a> مجموعةً من الأصناف التي تُمثِّل مكونات أكثر تعقيدًا بكثير من تلك التي رأيناها، ولكن حتى أكثر تلك المكونات تعقيدًا ليس صعب الاستخدام في غالبية الأحوال. سنناقش خلال هذا المقال بعض مكونات الواجهة التي تدعم عرض القوائم والجداول ومعالجتها؛ ولكي تتمكَّن من استخدام تلك المكونات المعقدة بفعالية، عليك أن تتعلم القليل عن نمط "نموذج-عرض-متحكِّم Model-View-Controller"، والذي يُعد أساس لكثيرٍ من مكونات واجهة المُستخدِم الرسومية. سنناقش هذا النمط لاحقًا ضمن هذا المقال.
</p>

<p>
	تُوفِّر مكتبة JavaFX عددًا من أدوات التحكُّم التي لن نتعرَّض لها بهذا الكتاب -على الرغم من أن بعضها مفيدٌ نوعًا ما وقد ترغب بالإطلاع عليه-، مثل <code>TabbedPane</code> و <code>SplitPane</code> و <code>Tree</code> و <code>ProgressBar</code>، وكذلك بعض أدوات التحكُّم لقراءة أنواع خاصة من المُدْخَلات، مثل <code>ColorPicker</code> و <code>DatePicker</code> و <code>PasswordField</code> و <code>Spinner</code>.
</p>

<p>
	سنبدأ هذا المقال بمثالٍ قصير على كتابة أداة تحكُّم مُخصَّصة، وهو أمرٌ ستحتاج إليه إذا لم تَجِد المُكوِّن الذي تريده ضمن التشكيلة الضخمة من مكونات الواجهة المُعرَّفة مُسبقًا بمكتبة JavaFX، أو من الممكن أن تجده فعلًا ولكنه معقدٌ جدًا بالموازنة مع متطلبات برنامجك، وقد ترغب مثلًا بشيء أبسط.
</p>

<h2>
	مكون واجهة مخصص بسيط
</h2>

<p>
	ستَجِد عادةً كل ما تحتاجه لإنشاء واجهة مُستخدِم رسومية بأصناف المكونات القياسية الموجودة بمكتبة JavaFX، ومع ذلك قد ترغب أحيانًا بشيءٍ مختلفٍ بعض الشيء. في تلك الحالة، قد تفكر بكتابة مُكوِّنك الخاص بالاعتماد على واحدة من المكونات التي تُوفِّرها المكتبة، أو بالاعتماد على الصنف البسيط <code>Control</code> الذي يَعمَل صنفًا أساسيًا base class لجميع أدوات التحكُّم.
</p>

<p>
	لنفترض مثلًا أننا نريد أداة تحكُّم تمثِّل "ساعة إيقاف"؛ فعندما ينقُر المُستخدِم على الساعة، ينبغي أن تُشغِّل الوقت؛ وعندما ينقر المُستخدِم عليها مرةً أخرى، ينبغي أن تَعرِض الزمن المُنقضِي منذ النقرة الأولى. يُمكِننا استخدام الصنف <code>Label</code> لعرض النص، ولكننا نريده أن يكون قادرًا على الاستجابة إلى حدث النقر على الفأرة، ويُمكِننا تحقيق ذلك بتعريف صنف مُكوِّن واجهة، وليكن اسمه هو <code>StopWatchLabel</code> صنفًا فرعيًا subclass مُشتقًا من الصنف <code>Label</code>، إذ سيَستمِع كائن الصنف <code>StopWatchLabel</code> لحدث نقر الفأرة عليه، ويُغيّر النص المعروض إلى "Timing…‎" عندما ينقر المُستخدِم عليه لأول مرة، كما سيتذكّر توقيت نقر المُستخدِم عليه. وعندما ينقر المُستخدِم عليه مرةً أخرى، سيفحص التوقيت مرةً أخرى، وسيحسِب الزمن المُنقضِي ويعرضه.
</p>

<p>
	في الواقع، لسنا في حاجة بالضرورة لتعريف صنفٍ فرعي جديد، إذ يمكننا استخدام عنوانٍ عادي بالبرنامج، وتهيئة مُستمعٍ ليَستجيب لحدث النقر على العنوان، ونَسمَح للبرنامج بإنجاز العمل اللازم للاحتفاظ بالزمن وتعديل النص المعروض بالعنوان. ومع ذلك، فبكتابة صنف جديد، سنتمكَّن من إعادة استخدامه بمشروعات أخرى، كما أن كل الشيفرة المُتعلقة بساعة الإيقاف مُجمعّةٌ معًا بمكانٍ واحد. يكون ذلك أكثر أهميةً عند التعامل مع المكونات الأكثر تعقيدًا.
</p>

<p>
	ليست كتابة الصنف <code>StopWatchLabel</code> بهذه الصعوبة، إذ سنحتاج إلى متغير نسخة instance variable لتخزين توقيت بدء تشغيل ساعة الإيقاف، وسنُهيئ تابعًا لمعالجة حدث النقر على ساعة الإيقاف. ينبغي أن يُحدِّد ذلك التابع ما إذا كانت ساعة الإيقاف مُشغَّلةً أم مُتوقفِة؛ ولهذا سنحتاج إلى متغير نسخة من النوع <code>boolean</code>، وليكن اسمه هو <code>running</code> للاحتفاظ بهذا الجانب من حالة المكوّن؛ كما سنَستخدِم التابع <code>System.currentTimeMillis()‎</code> للحصول على التوقيت الحالي بوحدة الميلي ثانية مثل قيمةٍ من النوع <code>long</code>. عند بدء تشغيل ساعة الإيقاف، سنخزِّن التوقيت الحالي بمتغير نسخة اسمه <code>startTime</code>؛ وعند إيقافها، سنَستخدِم التوقيت الحالي لحساب الزمن المنقضِي الذي ظلت خلاله ساعة الإيقاف قيد التشغيل. ألقِ نظرةً على شيفرة الصنف <code>StopWatch</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_7" style="">
<span class="kwd">import</span><span class="pln"> javafx</span><span class="pun">.</span><span class="pln">scene</span><span class="pun">.</span><span class="pln">control</span><span class="pun">.</span><span class="typ">Label</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">StopWatchLabel</span><span class="pln"> extends </span><span class="typ">Label</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">long</span><span class="pln"> startTime</span><span class="pun">;</span><span class="pln">   </span><span class="com">// Start time of timer.</span><span class="pln">
                              </span><span class="com">//   (Time is measured in milliseconds.)</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> boolean running</span><span class="pun">;</span><span class="pln">  </span><span class="com">// True when the timer is running.</span><span class="pln">

    </span><span class="com">// 2</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">StopWatchLabel</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="str">"  Click to start timer.  "</span><span class="pun">);</span><span class="pln">
        setOnMousePressed</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> setRunning</span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln">running </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="com">// 3</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> boolean isRunning</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> running</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="com">// 4</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> setRunning</span><span class="pun">(</span><span class="pln"> boolean running </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">running </span><span class="pun">==</span><span class="pln"> running</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">running </span><span class="pun">=</span><span class="pln"> running</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="com">// Record the time and start the timer.</span><span class="pln">
            startTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">();</span><span class="pln">  
            setText</span><span class="pun">(</span><span class="str">"Timing...."</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="com">// 5</span><span class="pln">
            </span><span class="kwd">long</span><span class="pln"> endTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">double</span><span class="pln"> seconds </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">endTime </span><span class="pun">-</span><span class="pln"> startTime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">1000.0</span><span class="pun">;</span><span class="pln">
            setText</span><span class="pun">(</span><span class="pln"> </span><span class="typ">String</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="str">"Time: %1.3f seconds"</span><span class="pun">,</span><span class="pln"> seconds</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end StopWatchLabel</span></pre>

<p>
	حيث تشير كل من:
</p>

<ul>
<li>
		[1] إلى مكوِّن واجهة مُخصَّص يمثِّل ساعة إيقاف بسيطة، فعندما ينقر المُستخدِم عليه، سيبدأ المؤقت بالعمل؛ وعندما ينقر المُستخدِم عليه مجددًا، يَعرِض الزمن بين النقرتين. يؤدي النقر لمرة ثالثة إلى بدء المؤقت من جديد، وهكذا. وبينما يكون المؤقت قيد التشغيل، يَعرِض العنوان الرسالة النصية "Timing….‎" فقط.
	</li>
	<li>
		[2] إلى أنه يضبط الباني النص المبدئي للعنوان إلى "Click to start timer"، ويُهيئ معالجًا لحدث النقر على الفأرة لكي يتمكَّن العنوان من الاستجابة إلى نقرات الفأرة.
	</li>
	<li>
		[3] يُشير إلى ما إذا المؤقت قيد التشغيل حاليًا.
	</li>
	<li>
		[4] أن المؤقت يُضبط ليَعمَل أو ليتوقف، ويُعدِّل النص المعروض بالعنوان، إذ ينبغي أن يُستدعى هذا التابع ضمن خيط تطبيق مكتبة JavaFX. يُحدِّد المعامل <code>running</code> ما إذا كان ينبغي أن يكون المؤقت قيد التشغيل؛ وإذا كانت قيمة المعامل تساوي حالته الحالية، لا يحدث أي شيء.
	</li>
	<li>
		[5] أنه قد أوقِف المؤقت، واحسب الزمن المنقضِي منذ لحظة بدء المؤقت واعرضه.
	</li>
</ul>
<p>
	نظرًا لأن الصنف <code>StopWatchLabel</code> هو صنفٌ فرعيٌ من الصنف <code>Label</code>، يُمكِننا تطبيق أيٍّ مما يُمكِننا فعله بكائنات الصنف <code>Label</code> على كائنات هذا الصنف؛ إذ يُمكِننا مثلًا إضافته إلى حاوية، أو أن نضبُط نوع الخط المُستخدَم، أو لونه، أو حجمه الأقصى، أو المُفضَّل، أو أن نضبُط تنسيق CSS الخاص به؛ كما يُمكِننا أيضًا أن نضبُط النص المعروض بداخله، مع أن ذلك يتعارض مع وظيفة ساعة الإيقاف.
</p>

<p>
	لاحِظ أن الصنف <a href="https://math.hws.edu/javanotes/source/chapter13/StopWatchLabel.java" rel="external nofollow">StopWatchLabel.java</a> ليس تطبيقًا، ولا يمكن تشغيله بمفرده. يَستخدِم البرنامج القصير <a href="https://math.hws.edu/javanotes/source/chapter13/TestStopWatch.java" rel="external nofollow">TestStopWatch.java</a> كائنًا من ذلك الصنف، ويَضبُط مجموعةً من خاصياته لتحسين مظهره.
</p>

<h2>
	نمط MVC
</h2>

<p>
	يُعدّ تقسيم المسؤوليات والمهام واحدًا من أهم مبادئ التصميم كائني التوجه object-oriented design؛ إذ ينبغي أن يكون لكل كائن دورًا وحيدًا محدّدًا بوضوح ومُقيدًّا بمسؤولية معينة؛ ويُعدّ نمط نموذج-عرض-مُتحكِّم Model-View-Controller -أو اختصارًا MVC- تطبيقًا جيدًا لهذا المبدأ على تصميم <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">واجهات المُستخدِم الرسومية</a>، إذ يشير كُل من النموذج والعرض والمُتحكِّم، إلى واحدةٍ من المسؤوليات الثلاث الضرورية لتصميم واجهات المُستخدِم الرسومية.
</p>

<p>
	إذا طبقنا نمط MVC على مكون، فسيتكوَّن النموذج من البيانات المُمثِّلة للحالة الحالية للمكوِّن؛ أما العرض فسيكون ببساطةٍ هو التمثيل المرئي للمكون على الشاشة؛ بينما سيشير المُتحكِّم إلى ذلك الجزء من المكون المسؤول عن فعل ما هو ضروري نتيجةً للأحداث الصادرة عن أفعال المُستخدِم، أو عن مصادر أخرى مثل المؤقتات. يُمكِن تلخيص فكرة ذلك النمط في إسناد مسؤولية كلٍ من النموذج والعرض والمُتحكِّم إلى كائناتٍ مختلفة.
</p>

<p>
	من السهل فهم دور العرض view بنمط MVC، وهو يُمثَّل عادةً باستخدام كائن المكوِّن ذاته، وتتلخص مسؤوليته في رسم المكون على الشاشة؛ ولكي يتمكَّن من إنجاز ذلك، فإنه يعتمد على النموذج، وذلك لاحتواءه على حالة المكوِّن الحالية التي تؤثر بلا شك على طريقة عرض المكوِّن على الشاشة.
</p>

<p>
	نظرًا لأن بيانات النموذج مُخزَّنةٌ بكائنٍ منفصل طبقًا لما ينص عليه نمط MVC، فينبغي للكائن المُمثِّل للمكوِّن الاحتفاظ بمرجع reference إلى الكائن المُمثِّل للنموذج. وعندما يتغير ذلك الكائن، تكون إعادة رسم العرض ضروريةً في العادة، وذلك لتعكس الحالة الجديدة، وبالتالي يحتاج المكوِّن إلى طريقةٍ لتحديد توقيت حدوث مثل تلك التغييرات؛ وهو ما يُمكِن تحقيقه بالاستعانة بالأحداث events و<a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r690" rel="">مستمعي الأحداث</a>.
</p>

<p>
	يُهيَأ كائن النموذج، فيُولِّد أحداثًا عند تغيُّر البيانات، ويُسجِّل كائن العرض نفسه مستمعًا لتلك الأحداث؛ وعندما يتغير النموذج، يقع حدث، ويُبلَّغ العرض بوقوعه؛ وبالتالي يكون بإمكانه الاستجابة بتحديث محتويات المكوِّن على الشاشة.
</p>

<p>
	عند استخدام نمط MVC مع مكونات مكتبة JavaFX، لا يكون المُتحكِّم مُنفصلًا بوضوح عن كلٍ من العرض والنموذج، إذ تُوزَّع وظيفته عادةً بين عدة كائنات. في العموم، قد يتضمَّن المُتحكِّم مستمعي أحداث الفأرة ولوحة المفاتيح المسؤولين عن الاستجابة لما يفعله المُستخدِم بالعرض؛ كما قد يتضمَّن مستمعي بعض الأحداث الأخرى عالية المستوى، مثل تلك الناتجة عن زر أو مزلاج، والتي تؤثر على حالة المكوِّن. ويَستجيب المُتحكِّم عادةً على الأحداث بإجراء تعديلات على النموذج، مما يؤدي إلى تعديل العرض مباشرةً استجابةً لتلك التغييرات التي أُجريَت على النموذج.
</p>

<p>
	تَستخدِم مكتبة JavaFX نمط MVC بأماكن كثيرة حتى لو لم تكن تَستخدِم مصطلحات "النموذج" و"العرض"؛ وتُعدّ الخاصيات القابلة للمراقبة observale -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1525/" rel="">الخاصيات والارتباطات في جافا</a>- أسلوبًا لتنفيذ فكرة النموذج المنفصل عن العرض، على الرغم من أن النموذج قد يكون موزَّعًا على كائناتٍ مختلفة كثيرة عند استخدام الخاصيات. في الواقع، ستلاحِظ وضوح دور كُلٍ من النموذج والعرض أكثر بأداتي القائمة والجدول اللتين سنناقشهما فيما يلي.
</p>

<h2>
	صنفا القائمة ListView والجدول ComboBox
</h2>

<p>
	يُمثِّل الصنف <code>ListView</code> قائمةً من العناصر التي يستطيع المُستخدِم أن يختار من بينها، كما بإمكانه أن يُعدِّل العناصر الموجودة بالقائمة. يَسمَح البرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/SillyStamper.java" rel="external nofollow">SillyStamper.java</a> للمُستخدِم باختيار أيقونة (صورة صغيرة) من قائمة أيقونات مُمثَّلةٍ بكائنٍ من النوع <code>ListView</code>؛ بحيث يختار المُستخدِم الأيقونة التي يرغب بها بالنقر عليها، ثم يُمكِنه أن يَطبَعها داخل الحاوية من خلال النقر على الحاوية. وفي المقابل، يُضيف النقر مع الضغط على زر Shift نسخةً أكبر من الصورة إلى الحاوية (الأيقونات المُستخدَمة بهذا البرنامج مأخوذة من مشروع سطح المكتب KDE).
</p>

<p>
	تَعرِض الصورة التالية نافذة البرنامج بعد أن طَبعَ المُستخدِم مجموعةً من الأيقونات فعليًا داخل مساحة الرسم، واختارَ أيقونة "star" من القائمة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95854" href="https://academy.hsoub.com/uploads/monthly_2022_04/001Silly_Stamper.png.18ddfd1dd40143e756aa0db6f5c679b0.png" rel=""><img alt="001Silly_Stamper.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95854" data-unique="fqri3fyqh" src="https://academy.hsoub.com/uploads/monthly_2022_04/001Silly_Stamper.png.18ddfd1dd40143e756aa0db6f5c679b0.png"></a>
</p>

<p>
	ستَجِد الصنف <code>ListView</code> مُعرَّفًا بحزمة <code>javafx.scene.control</code>؛ وهو في الحقيقة صنفٌ ذو معاملات غير محددة النوع parameterized، إذ يشير معامل النوع إلى نوع الكائن المعروض بالقائمة، ويُعدّ النوع <code>ListView&lt;String&gt;‎</code> هو الأكثر شيوعًا؛ ولكننا استخدمنا بهذا البرنامج النوع <code>ListView&lt;ImageView&gt;‎</code>، إذ تستطيع كائنات النوع <code>ListView</code> أن عرض السلاسل النصية من النوع <code>String</code> والعقد من النوع <code>Node</code> مباشرةً؛ وعند استخدامه مع كائنات من أنواع أخرى، فإنه يَعرِض التمثيل النصي للكائن الذي يعيده التابع <code>toString()‎</code> افتراضيًا، والذي لا يكون مفيدًا في غالب الأحيان.
</p>

<p>
	تُخزَّن عناصر القائمة من النوع <code>ListView&lt;T&gt;‎</code> بكائنٍ من النوع <code>ObservableList&lt;T&gt;‎</code>، إذ تُعدّ قائمة العناصر جزءًا من النموذج الخاص بالمكون، ويُمكِننا استخدام التابع <code>listView.getItems()‎</code> لاسترجاع عناصر القائمة؛ وعند إضافة العناصر إلى تلك القائمة أو حذفها منها، تُحدَّث القائمة تلقائيًا لتَعكِس ذلك التغيير.
</p>

<p>
	يُعرِّف البرنامج <code>SillyStamper</code> القائمة باستخدام المُعدِّل static؛ مما يَعنِي أنه من غير الممكن تعديل القائمة بعد إنشائها، وبالتالي لا يستطيع المُستخدِم أن يُعدِّل القائمة.
</p>

<p>
	يقرأ البرنامج صور الأيقونات من ملفات موراد، ويحيط كل صورةٍ منها ضمن كائنٍ من النوع <code>ImageView</code> -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1148/" rel="">مكونات التحكم البسيطة في واجهة المستخدم في مكتبة جافا إف إكس JavaFX</a>-، ويُضيفه إلى قائمة العناصر بكائن الصنف <code>ListView</code>.
</p>

<p>
	تَعرِض الشيفرة التالية التابع المسؤول عن إنشاء القائمة، والذي يستدعِيه التابع <code>start()‎</code> الخاص بهذا البرنامج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_11" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="typ">ListView</span><span class="pun">&lt;</span><span class="typ">ImageView</span><span class="pun">&gt;</span><span class="pln"> createIconList</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> iconNames </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
               </span><span class="com">// ‫أسماء ملفات الموارد بالمجلد stamper_icons</span><span class="pln">
            </span><span class="str">"icon5.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon7.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon8.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon9.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon10.png"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"icon11.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon24.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon25.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon26.png"</span><span class="pun">,</span><span class="pln"> 
            </span><span class="str">"icon31.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon33.png"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"icon34.png"</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">

    iconImages </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">[</span><span class="pln">iconNames</span><span class="pun">.</span><span class="pln">length</span><span class="pun">];</span><span class="pln">  </span><span class="com">// لرسم الأيقونات</span><span class="pln">

    </span><span class="typ">ListView</span><span class="pun">&lt;</span><span class="typ">ImageView</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">list</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ListView</span><span class="pun">&lt;&gt;();</span><span class="pln">

    </span><span class="typ">list</span><span class="pun">.</span><span class="pln">setPrefWidth</span><span class="pun">(</span><span class="lit">80</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">list</span><span class="pun">.</span><span class="pln">setPrefHeight</span><span class="pun">(</span><span class="lit">100</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> iconNames</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Image</span><span class="pln"> icon </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">(</span><span class="str">"stamper_icons/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> iconNames</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span><span class="pln">
        iconImages</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> icon</span><span class="pun">;</span><span class="pln">
        </span><span class="typ">list</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ImageView</span><span class="pun">(</span><span class="pln">icon</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="typ">list</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">().</span><span class="pln">select</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">  </span><span class="com">// اختر العنصر الأول بالقائمة</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">list</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يبدو أن الحجم المُفضَّل الافتراضي لأي قائمة هو 200 في 400 بغض النظر عن مكوناتها. ويضبُط التابع السابق العرض والطول المُفضَّلين للقائمة؛ فقائمة الأيقونات تحتاج عرضًا أصغر بكثير، كما أن الطول المُفضَّل الافتراضي يؤدي إلى زيادة طول الحاوية بقدرٍ أكبر مما هو مرغوب به، ولذلك يَضبُط التابع الطول المُفضَّل إلى قيمةٍ أصغر، مع أنها ستمتد ضمن هذا البرنامج لتملأ المساحة المُتاحة.
</p>

<p>
	يبدو استخدام التابع "للنموذج المُختار" ضمن القائمة مثيرًا بعض الشيء؛ ويُقصَد بذلك جزء النموذج الذي يحتوي على قائمة العناصر التي اختارها المُستخدِم من القائمة، إذ يستطيع المُستخدِم أن يختار عنصرًا واحدًا فقط على الأكثر افتراضيًا، وهو السلوك المناسب لهذا البرنامج؛ ولكن يُمكِننا عمومًا ضبطه ليسمح باختيار عدة عناصر بنفس الوقت، وذلك باستدعاء ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_13" style="">
<span class="typ">list</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">().</span><span class="pln">setSelectionMode</span><span class="pun">(</span><span class="typ">SelectionMode</span><span class="pun">.</span><span class="pln">MULTIPLE</span><span class="pun">);</span></pre>

<p>
	في حالة تطبيق وضع الاختيار الأحادي الافتراضي، يُلغَى اختيار العنصر المُختار حاليًا -إن وجد-، إذا اختار المُستخدِم عنصًرا آخرًا من القائمة؛ إذ يستطيع المُستخدِم اختيار عنصرٍ بالنقر عليه؛ كما يستطيع البرنامج ضبط الاختيار إلى عنصرٍ معينٍ باستدعاء التابع <code>list.getSelectionModel().select(index)‎</code>، إذ يشير المعامل <code>index</code> إلى فهرس العنصر المطلوب اختياره. يبدأ هنا ترقيم العناصر من الصفر، وفي حالة كان <code>index</code> يُساوِي <code>-1</code>، فسيُلغَى الاختيار من جميع العناصر.
</p>

<p>
	يُسلًط عرض القائمة الضوء على العنصر المُختار حاليًا، كما يستمع إلى التغييرات الحادثة بالنموذج المختار؛ فعندما ينقر المُستخدِم على عنصر، سيُعدِّل مستمع أحداث الفأرة (يُمثِل جزءًا من النموذج) النموذج المختار، ويُبلَّغ العرض بحدوث ذلك التغيير؛ وبناءً على ذلك، يُحدِّث العرض مظهره ليَعكِس حقيقة اختيار عنصرٍ آخر. يستطيع البرنامج استرجاع العنصر الواقع عليه الاختيار حاليًا باستدعاء التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_15" style="">
<span class="typ">list</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">().</span><span class="pln">getSelectedIndex</span><span class="pun">()</span></pre>

<p>
	يَستخدِم البرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/SillyStamper.java" rel="external nofollow">SillyStamper.java</a> التابع السابق عندما ينقر المُستخدِم على الحاوية؛ ولكي يُحدِّد أي أيقونةٍ ينبغي عليه أن يطبعها بالصورة.
</p>

<p>
	لاحِظ أن القائمة بالبرنامج SillyStamper.java غير قابلةٍ للتعديل، لكن يحتوي البرنامج التوضيحي الثاني <a href="https://math.hws.edu/javanotes/source/chapter13/EditListDemo.java" rel="external nofollow">EditListDemo.java</a> على قائمتين بإمكان المُستخدِم تعديلهما: الأولى قائمة سلاسلٍ نصية، والأخرى قائمة أعداد؛ إذ يستطيع المُستخدِم أن يبدأ بتعديل عنصرٍ معينٍ ضمن القائمة بالنقر المزدوج عليه، أو بالنقر عليه مرةً واحدةً إذا كان قيد الاختيار أساسًا؛ كما يُمكِنه إنهاء عملية التعديل من خلال الضغط على مفتاح "return" أو مفتاح "escape" لإلغاء عملية التعديل. ويؤدي اختيار عنصرٍ آخر ضمن القائمة إلى إلغاء عملية التعديل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95855" href="https://academy.hsoub.com/uploads/monthly_2022_04/002Edit_List_Demo.png.03c9365e5549db9085309de11f643405.png" rel=""><img alt="002Edit_List_Demo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95855" data-unique="oyx82hm27" src="https://academy.hsoub.com/uploads/monthly_2022_04/002Edit_List_Demo.png.03c9365e5549db9085309de11f643405.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	هناك بعض الأشياء التي ينبغي فعلها إذا أردنا السماح للمُستخدِم بتعديل عناصر قائمةٍ من النوع <code>ListView</code>. أولًا، ينبغي أن نجعل القائمة قابلةً للتعديل باستدعاء التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_18" style="">
<span class="typ">list</span><span class="pun">.</span><span class="pln">setEditable</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span></pre>

<p>
	ولكن هذا ليس كافيًا، إذ ينبغي أن تكون كل خلية ضمن القائمة قابلةً للتعديل أيضًا؛ ويُقصَد بالخلية هنا تلك المساحة الموجودة بالقائمة، والتي يُعرَض من خلالها عنصرٌ وحيد. في العموم، كل خلية هي كائنٌ مسؤولٌ عن عرض العنصر، وبإمكانه أيضًا تعديله وفق ما يفعله المُستخدِم، ولكن لاحِظ أن تلك الخلايا لا تكون قابلةً للتعديل بالوضع الافتراضي.
</p>

<p>
	تَستخدِم القوائم من النوع <code>ListView</code> <a href="https://wiki.hsoub.com/Design_Patterns/abstract_factory" rel="external">مصنع factory خلايا</a> لإنشاء الكائنات التي تُمثِّل تلك الخلايا. ففي الواقع، يُعَد مصنع الخلايا كائنًا آخر، ووظيفته هي إنشاء الخلايا؛ ولكي نَحصل على نوعٍ مختلفٍ من الخلايا، ينبغي أن نُوفِّر للقائمة مصنع خلايا مختلف. يتبِع ذلك ما يُعرَف باسم نمط المصنع factory pattern؛ فمن خلال استخدام كائن مصنع لإنشاء الخلايا، يُمكِننا أن نُخصِّص الخلايا بسهولة دون تغيير الشيفرة المصدرية للصنف <code>ListView</code>، فكل ما نحتاج إليه هو مصنع خلايا جديد.
</p>

<p>
	في الواقع، ليس من السهل كتابة مصانع الخلايا، ولكن تُوفِّر مكتبة JavaFX لحسن الحظ مجموعةً من مصانع الخلايا القياسية. فإذا كان <code>listView</code> من النوع <code>ListView&lt;String&gt;‎</code>، فيُمكِننا أن نُهيِئ مصنع خلايا بإمكانه إنشاء خلايا قابلةٍ للتعديل باستدعاء ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_20" style="">
<span class="pln">listView</span><span class="pun">.</span><span class="pln">setCellFactory</span><span class="pun">(</span><span class="pln"> </span><span class="typ">TextFieldListCell</span><span class="pun">.</span><span class="pln">forListView</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُعيد التابع <code>TextFieldListCell.forListView()‎</code> مصنعًا بإمكانه إنشاء خلايا تَعرِض سلاسلًا نصيةً وتُعدِّلها؛ إذ تَستخدِم الخلية عنوانًا من النوع <code>Label</code> أثناء عرض السلسلة النصية، وتَستخدِم حقلًا نصيًا من النوع <code>TextField</code> أثناء تعديل العنصر.
</p>

<p>
	هذا هو كل ما ينبغي أن تعرفه لكي تتمكَّن من إنشاء قائمة سلاسل نصية قابلةٍ للتعديل. وعلاوةً على ذلك، تتوفَّر أنواع عناصر أخرى يتناسب معها أيضًا عرض العنصر، مثل سلسلةٍ نصية واستخدام حقلٍ نصي من النوع <code>TextField</code> أثناء تعديل العنصر، مثل الأعداد والقيم المكونة من محرف واحد والتواريخ والأزمنة. ومع ذلك، إذا لم يكن العنصر سلسلةً نصيةً، فلا بُدّ من وجود طريقةٍ ما للتحويل بينه وبين تمثيله النصي. تُسهِّل مكتبة JavaFX لحسن الحظ تحقيق ذلك بالحالات الشائعة إلى حدٍ كبير، فهي تُوفِّر مُحوِّلات قياسية لجميع الأنواع المذكورة بالأعلى. على سبيل المثال، إذا كان <code>intList</code> قائمةً قابلةً للتعديل من النوع <code>ListType&lt;Integer&gt;‎</code>، فيُمكِننا أن نُهيِئ له مصنع خلايا مناسب باستخدام التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_22" style="">
<span class="pln">intView</span><span class="pun">.</span><span class="pln">setCellFactory</span><span class="pun">(</span><span class="pln"> 
           </span><span class="typ">TextFieldListCell</span><span class="pun">.</span><span class="pln">forListView</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IntegerStringConverter</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذ أن المعامل المُمرَّر للتابع <code>forListView</code> هو كائنٌ يُحوِّل بين الأعداد الصحيحة وتمثيلاتها النصية. ونظرًا لأن ذلك المُحوِّل القياسي لا يُعالِج المُدْخَلات غير الصالحة بطريقةٍ جيدة، فقد اِستخدَمنا بالبرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/EditListDemo.java" rel="external nofollow">EditListDemo.java</a> مُحوِّلًا مُخصَّصًا آخرًا -كتبه المؤلف- لمصنع الخلايا المُستخدَم بقائمة الأعداد الصحيحة ضمن البرنامج. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_24" style="">
<span class="typ">StringConverter</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> myConverter </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">StringConverter</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// 1        </span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Integer</span><span class="pln"> fromString</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> s</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// حوِّل سلسلةً نصيةً إلى عدد صحيح</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">s </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">||</span><span class="pln"> s</span><span class="pun">.</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">length</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">.</span><span class="pln">parseInt</span><span class="pun">(</span><span class="pln">s</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">NumberFormatException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> toString</span><span class="pun">(</span><span class="typ">Integer</span><span class="pln"> n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// حوِّل عددًا صحيحًا إلى سلسلة نصية</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">n </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Bad Value"</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

listView</span><span class="pun">.</span><span class="pln">setCellFactory</span><span class="pun">(</span><span class="pln"> </span><span class="typ">TextFieldListCell</span><span class="pun">.</span><span class="pln">forListView</span><span class="pun">(</span><span class="pln"> myConverter </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذ تشير [1] إلى أن مُحوِّل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">السلاسل النصية</a> المُخصَّص يحوّل قيمةً نصيةً مُدخَلةً غير صالحة إلى القيمة الفارغة <code>null</code> بدلًا من الفشل، ويَعرِض تلك القيمة الفارغة <code>null</code> على هيئة "Bad Value"؛ بينما يَعرِض السلسلة النصية الفارغة على هيئة صفر.
</p>

<p>
	يُعرِّف الصنف <code>StringConverter</code> تابعين فقط، هما <code>toString()‎</code> و <code>fromString()‎</code>.
</p>

<p>
	ستَجِد محولات السلاسل النصية القياسية مُعرَّفةً ضمن حزمة <code>javafx.util.converters</code>؛ كما ستَجِد الصنف <code>TextFieldListCell</code> مُعرَّفًا ضمن حزمة <code>javafx.scene.control.cell</code>؛ كما تتوفَّر أيضًا أصنافٌ أخرى مشابهة من أجل الخلايا المُستخدَمة مع الجداول.
</p>

<p>
	إلى جانب القوائم، يُنفِّذ البرنامج التوضيحي بعض الأشياء الأخرى الشيّقة المُتعلّقة بالأزرار والعناوين باستخدامه لبعض الخاصيات القابلة للمراقبة observable المُعرَّفة بنموذج القائمة -كما ناقشنا بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1525/" rel="">الخاصيات والارتباطات في جافا</a>-؛ إذ يحتوي البرنامج مثلًا على عناوين تَعرِض العنصر المُختار ورقمه، وقد نفَّذ البرنامج ذلك بربط خاصية <code>text</code> الموجودة بالعنوان مع خاصيةٍ مُعرَّفةٍ بالنموذج المُختار الموجود بالقائمة. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_28" style="">
<span class="typ">Label</span><span class="pln"> selectedIndexLabel </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">();</span><span class="pln">
selectedIndexLabel</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln">
                    listView</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">selectedIndexProperty</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">asString</span><span class="pun">(</span><span class="str">"Selected Index: %d"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

</span><span class="typ">Label</span><span class="pln"> selectedNumberLabel </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">();</span><span class="pln">
selectedNumberLabel</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln">
                    listView</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">selectedItemProperty</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">asString</span><span class="pun">(</span><span class="str">"SelectedItem: %s"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	لا بُدّ أن يكون الزر المسؤول عن حذف عنصر القائمة المختار حاليًا مُفعَّلًا فقط في حالة وجود عنصرٍ مُختارٍ فعلًا، إذ يُنفِّذ البرنامج ذلك بربط خاصية الزر <code>disable</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_30" style="">
<span class="pln">deleteNumberButton</span><span class="pun">.</span><span class="pln">disableProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> 
                    listView</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">selectedIndexProperty</span><span class="pun">()</span><span class="pln">
                            </span><span class="pun">.</span><span class="pln">isEqualTo</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	وبالتالي، تُمثِّل العناوين والأزرار بدائلًا لنفس النموذج المختار الذي تعتمد عليه القائمة، إذ يُعدّ ذلك واحدًا من أهم خاصيات نمط MVC، ألا وهو: قد تتواجد عدة عروض views لنفس الكائن المُمثِّل لنموذجٍ معين.
</p>

<p>
	تستمع العروض للتغييرات الحادثة بالنموذج؛ وفي حالة حدوث تعديل، تُبلَّغ العروض بالتغيير الحادث، وتُحدِّث نفسها لتعكِس الحالة الجديدة للنموذج. وبالإضافة إلى ما سبق، يحتوي البرنامج على الزر "Add" المسؤول عن إضافة عنصرٍ جديدٍ إلى القائمة، إذ يَستخدِم ذلك الزر جزءًا آخرًا من نموذج كائن الصنف <code>ListView</code> المُمثِّل للقائمة؛ وذلك بإضافة العنصر إلى كائنٍ قابلٍ للمراقبة من النوع <code>ObservableList</code> يَحمِل جميع عناصر القائمة. ونظرًا لأن كائن الصنف <code>ListView</code> يستمع إلى التغييرات الواقعة بتلك القائمة القابلة للمراقبة، فإنه يُبلَّغ بحدوث ذلك التغيير، وبالتالي يُمكِنه أن يُحدِّث نفسه ليعرِض العنصر الجديد ضمن القائمة. وبخلاف إضافة العنصر إلى القائمة القابلة للمراقبة، فلا حاجة لفعل أي شيءٍ آخر لإظهار العنصر على الشاشة.
</p>

<p>
	والآن، سنناقش أداة تحكُّم أخرى مُمثَّلةٍ بالصنف <code>ComboBox</code>، إذ تشبه تلك الأداة أداة التحكُّم التي يُمثِّلها الصنف <code>ListView</code> إلى حدٍ كبير، بل هي أساسًا نفس أداة <code>ListView</code>، ولكنها تُظهِر العنصر المُختار فقط؛ فعندما ينقر المُستخدِم على تلك الأداة، ستَظهَر قائمةٌ بجميع العناصر المتاحة، ويستطيع المُستخدِم أن يختار أي عنصرٍ منها. في الواقع، تَستخدِم أداة التحكُّم <code>ComboBox</code> كائنًا من الصنف <code>ListView</code> لعرض القائمة التي تظهر عند نَقْر المُستخدِم على الأداة. ولقد رأينا تلك الأداة مُستخدَمةً فعلًا بهيئة قائمة ببعض الأمثلة السابقة، مثل البرنامج <a href="https://math.hws.edu/javanotes/source/chapter1/GUIDemo.java" rel="external nofollow">GUIDemo.java</a> بمقال <a href="https://academy.hsoub.com/programming/java/%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r967/" rel="">واجهة المستخدم الحديثة في جافا</a>. بالمثل من الصنف <code>ListView</code>، إذ يُعدّ الصنف <code>ComboBox</code> نوعًا ذا معاملات غير محدَّدة النوع، ويُعدّ نوع العنصر <code>String</code> هو الأكثر اِستخدامًا معها، على الرغم من دعمها لأنواع عناصر أخرى (باستخدام مصانع الخلايا ومحوّلات السلاسل النصية).
</p>

<p>
	يُمكِننا إنشاء أداة تحكُّم من النوع <code>ComboBox</code> وإدارتها بنفس طريقة إنشاء وإدارة أداة التحكُّم <code>ListView</code>. ألقِ نظرةً على الشيفرة التالية على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_32" style="">
<span class="typ">ComboBox</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">&gt;</span><span class="pln"> flavors </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ComboBox</span><span class="pun">&lt;&gt;();</span><span class="pln">
flavors</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">addAll</span><span class="pun">(</span><span class="str">"Vanilla"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Chocolate"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Strawberry"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Pistachio"</span><span class="pun">);</span><span class="pln">
flavors</span><span class="pun">.</span><span class="pln">getSelectionModel</span><span class="pun">().</span><span class="pln">select</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	يُمكِننا ضبط تلك الأداة لتُصبِح قابلةً للتعديل، ولسنا بحاجةٍ إلى مصنع خلايا مُخصَّص لذلك الغرض طالما كانت العناصر المُستخدَمة من النوع <code>String</code>، وتكون الأداة في هذه الحالة أشبه بتركيبةٍ غريبة تجمع بين الحقل النصي والقائمة؛ إذ تَستخدِم حقلًا نصيًا لعرض العنصر المُختار بدلًا من استخدام عنوان. إلى جانب ذلك، يمكن للمُستخدِم تعديل قيمة الحقل النصي، وستُصبِح القيمة المُعدَّلة هي القيمة المختارة، ولكن لاحظ أن القيمة الأصلية للعنصر المُعدَّل لا تُحذَّف من القائمة، وإنما يُضاف العنصر الجديد فقط، كما أن العنصر الجديد لا يُصبِح جزءًا دائمًا من القائمة. يؤدي استدعاء التابع <code>flavors.setEditable(true)‎</code> في المثال السابق مثلًا، إلى السماح للمُستخدِم بكتابة "Rum Raisin,‎" أو أي شيء آخر على أنها نكهةٌ مُفضَّلة، ولكنه لا يحلّ محل العنصر "Vanilla"، أو "Chocolate"، أو "Strawberry"، أو "Pistachio" الموجودين بالقائمة.
</p>

<p>
	بخلاف كائنات الصنف <code>ListView</code>، تُولِّد كائنات الصنف <code>ComboBox</code> حدثًا من النوع <code>ActionEvent</code> عندما يختار المُستخدِم عنصرًا جديدًا سواءٌ فَعَلَ ذلك باختيار العنصر من القائمة، أو بكتابة العنصر على أنه قيمةٌ جديدةٌ بالصندوق القابل للتعديل، ثم الضغط على "return".
</p>

<h2>
	الصنف TableView
</h2>

<p>
	بالمثل من أداة التحكُّم بالقائمة المُمثَّلة بالصنف <code>ListView</code>، تَعرِض أداة تحكُّم "الجدول" المُمثَّلة بالصنف <code>TableView</code> تجميعةً من العناصر للمُستخدِم، ولكنها أكثر تعقيدًا، إذ تُرتَّب عناصر الجدول ضمن شبكةٍ من الصفوف والأعمدة، ويُمثِّل كل موضعٍ بالشبكة "خليةً" ضمن الجدول. يحتوي كل عمودٍ هنا على متتاليةٍ من العناصر، ويملُك رأسًا يقع أعلى العمود ويحتوي على اسمه. وفي العموم، يتشابه العمل مع عمودٍ واحد ضمن كائن الصنف <code>TableView</code> مع العمل مع كائن الصنف <code>ListView</code> من جوانب كثيرة.
</p>

<p>
	يُعدّ الصنف <code>TableView&lt;T&gt;‎</code> نوعًا ذا معاملات غير مُحدَّدة النوع، إذ يحتوي كائن معامل النوع <code>T</code> على جميع البيانات المتعلقة بصف واحد ضمن الجدول، ويُمكِنه أن يتضمَّن بيانات إضافية أيضًا؛ فيمكن للجدول أن يَكون "عرضًا view " لبعض البيانات المُتاحة فقط. ينتمي نموذج البيانات الخاص بجدول من النوع <code>TableView&lt;T&gt;‎</code> إلى الصنف <code>ObservableList&lt;T&gt;‎</code>، ويُمكِننا استرجاعه باستدعاء التابع <code>table.getItems()‎</code>، كما يُمكِننا أيضًا إضافة الصفوف إلى الجدول وحذفها منه بإضافة العناصر وحذفها من تلك القائمة.
</p>

<p>
	لكي نُعرِّف جدولًا: لا يكون تحديد الصنف المُمثِّل لصفوف الجدول كافيًا، فعلينا أيضًا أن نحدِّد نوع البيانات التي ننوي تخزينها بكل عمود ضمن الجدول؛ لذلك سنَستخدِم كائنًا من النوع <code>TableColumn&lt;T,S&gt;‎</code> لوصف كل عمود ضمن الجدول، إذ يشير معامل النوع الأول <code>T</code> إلى نفس نوع معامل النوع الخاص بالجدول، بينما يشير معامل النوع الثاني <code>S</code> إلى نوع العناصر التي ننوي تخزينها بخلايا ذلك العمود.
</p>

<p>
	يشير النوع <code>TableColumn&lt;T,S&gt;‎</code> إلى كون العمود يَعرِض عناصرًا من النوع <code>S</code> مشتقةً من صفوفٍ من النوع <code>T</code>. لا تحتوي الكائنات المُمثِّلة للعمود على العناصر المعروضة بالعمود، فهم موجودون بكائنات النوع <code>T</code> التي تُمثِّل الصفوف، ومع ذلك تحتاج كائنات الأعمدة إلى طريقةٍ لاسترجاع العنصر المعروض بالعمود من الكائن المُمثِّل للصف؛ إذ يُمكِننا إجراء ذلك بتخصيص ما يُعرَف باسم "مصنع قيم الخلايا"، فيُمكِننا مثلًا أن نكتب مصنعًا لتطبيق أي دالةٍ function على كائنٍ مُمثِّل لصف، ولكن الصنف <code>PropertyValueFactory</code> يُعدّ هنا الخيار الأكثر شيوعًا، والذي يسترجِع ببساطة قيمة إحدى خاصيات كائن الصف.
</p>

<p>
	والآن لنفحص مثالًا. يَعرِض البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/SimpleTableDemo.java" rel="external nofollow">SimpleTableDemo.java</a> جدولًا غير قابلٍ للتعديل، ويحتوي الجدول على أسماء الولايات الخمسين الموجودة بالولايات المتحدة الأمريكية مع عواصمها وتعدادها السكاني. ألقِ نظرةً على الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95856" href="https://academy.hsoub.com/uploads/monthly_2022_04/003Table_Demo.png.b1d7572c365ceaa5905108a33787898d.png" rel=""><img alt="003Table_Demo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95856" data-unique="rdb6h8nap" src="https://academy.hsoub.com/uploads/monthly_2022_04/003Table_Demo.png.b1d7572c365ceaa5905108a33787898d.png"></a>
</p>

<p>
	يَستخدِم البرنامج كائناتٍ تنتمي إلى الصنف <code>StateData</code> المُعرَّف على أنه صنفٌ متداخلٌ ساكنٌ عام لحَمْل بيانات كل صف. لا بُدّ أن يكون الصنف عامًا لكي نتمكَّن من اِستخدَامه مع الصنف <code>PropertyValueFactory</code>، ولكن ليس من الضروري أن يكون متداخلًا أو ساكنًا. سنُعرِّف قيم بيانات كل صف على أنها خاصيات ضمن ذلك الصنف، أي سيكون هنالك تابع جَلْب getter لكل قيمةٍ منها.
</p>

<p>
	في الحقيقة، يُعدّ تعريف الخاصيات باستخدام توابع جلب كافيًا لاستخدامها مثل قيمٍ بجدولٍ غير قابل للتعديل، كما سنحتاج إلى شيء مختلف بالنسبة لأعمدة الجداول القابلة للتعديل كما سنرى لاحقًا. ألقِ نظرةً على تعريف الصنف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_34" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">StateData</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> capital</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> population</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> getState</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> getCapital</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> capital</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> getPopulation</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> population</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">StateData</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> s</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> c</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> p</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        state </span><span class="pun">=</span><span class="pln"> s</span><span class="pun">;</span><span class="pln">
        capital </span><span class="pun">=</span><span class="pln"> c</span><span class="pun">;</span><span class="pln">
        population </span><span class="pun">=</span><span class="pln"> p</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنُنشِئ الجدول المسؤول عن عرض بيانات الولايات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_36" style="">
<span class="typ">TableView</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">&gt;</span><span class="pln"> table </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableView</span><span class="pun">&lt;&gt;();</span></pre>

<p>
	بعد ذلك سنضيف إلى نموذج بيانات الجدول عنصرًا لكل ولاية، والذي يُمكِننا استرجاعه باستدعاء التابع <code>table.getItems()‎</code>؛ ثم سنُنشِئ الكائنات المُمثِّلة للأعمدة ونُهيئها ونضيفها إلى نموذج أعمدة الجدول، والذي يُمكِننا استرجاعه باستدعاء التابع <code>table.getColumns()‎</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_38" style="">
<span class="typ">TableColumn</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pun">&gt;</span><span class="pln"> stateCol </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableColumn</span><span class="pun">&lt;&gt;(</span><span class="str">"State"</span><span class="pun">);</span><span class="pln">
stateCol</span><span class="pun">.</span><span class="pln">setCellValueFactory</span><span class="pun">(</span><span class="pln">
                </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PropertyValueFactory</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pun">&gt;(</span><span class="str">"state"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
table</span><span class="pun">.</span><span class="pln">getColumns</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">stateCol</span><span class="pun">);</span><span class="pln">

</span><span class="typ">TableColumn</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pun">&gt;</span><span class="pln"> capitalCol </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableColumn</span><span class="pun">&lt;&gt;(</span><span class="str">"Capital City"</span><span class="pun">);</span><span class="pln">
capitalCol</span><span class="pun">.</span><span class="pln">setCellValueFactory</span><span class="pun">(</span><span class="pln">
                  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PropertyValueFactory</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pun">&gt;(</span><span class="str">"capital"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
table</span><span class="pun">.</span><span class="pln">getColumns</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">capitalCol</span><span class="pun">);</span><span class="pln">

</span><span class="typ">TableColumn</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> populationCol </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableColumn</span><span class="pun">&lt;&gt;(</span><span class="str">"Population"</span><span class="pun">);</span><span class="pln">
populationCol</span><span class="pun">.</span><span class="pln">setCellValueFactory</span><span class="pun">(</span><span class="pln">
                 </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PropertyValueFactory</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;(</span><span class="str">"population"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
table</span><span class="pun">.</span><span class="pln">getColumns</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">populationCol</span><span class="pun">);</span></pre>

<p>
	يُمثِّل المعامل المُمرَّر لباني الصنف <code>TableColumn</code> النص المعروض برأس العمود. وبالنسبة لمصانع قيم الخلايا، يحتاج أي مصنعٍ منها إلى قراءة قيمة الخلية من كائن صفٍ ينتمي إلى النوع <code>StateData</code>؛ أما بالنسبة للعمود الأول، فنوع البيانات هو <code>String</code>، وبالتالي ينبغي أن يَستقبِل المصنع مُدْخَلًا من النوع <code>StateDate</code> ويُخرِج قيمة خاصية من النوع <code>String</code>. بالتحديد، الخرج هو قيمة الخاصية <code>state</code> المُعرَّفة ضمن كائن الصنف <code>StateData</code>. بالتالي، يُمكِننا كتابة الباني على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_40" style="">
<span class="kwd">new</span><span class="pln"> </span><span class="typ">PropertyValueFactory</span><span class="pun">&lt;</span><span class="typ">StateData</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pun">&gt;(</span><span class="str">"state"</span><span class="pun">)</span></pre>

<p>
	يُنشِئ الاستدعاء السابق مصنع قيم خلايا يَحصُل على القيمة التي سيَعرِضها بالخلية باستدعاء <code>obj.getState()‎</code>، إذ أن <code>obj</code> هو الكائن المُمثِّل لصف الجدول المُتضمِّن للخلية. وقد خصَّصنا العمودين الآخرين بنفس الطريقة.
</p>

<p>
	هذا هو كل ما تحتاج إلى معرفته لكي تتمكَّن من إنشاء جدولٍ لا يستطيع المُستخدِم أن يُعدِّل محتويات خلاياه. يمكن للمُستخدِم افتراضيًا تعديل طول عَرْض العمود من خلال سحب الفاصل الموجود بين رأسي أي عمودين؛ كما بإمكانه أن ينقر على رأس أي عمود لكي يُرتِّب صفوف الجدول ترتيبًا تصاعديًا أو تنازليًا وفقًا لقيم ذلك العمود؛ وبإمكاننا مع ذلك تعطيل هاتين الخاصيتين بضبط بعض الخاصيات المُعرَّفة بكائن الصنف <code>TableColumn</code> -وهو ما سنفعله بالمثال التالي-؛ كما يستطيع المُستخدِم أيضًا إعادة ترتيب الأعمدة بسحب رأس العمود إلى اليمين أو اليسار.
</p>

<p>
	يتضمَّن البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/ScatterPlotTableDemo.java" rel="external nofollow">ScatterPlotTableDemo.java</a> مثالًا على جدول قابل للتعديل، إذ يُمثِّل كل صفٍ ضمن الجدول نقطةً على سطح المستوى، ويحتوي العمودين على الإحداثي الأفقي والرأسي للنقاط. يَعرِض البرنامج تلك النقاط ضمن مخطط انتشار بياني scatter plot داخل حاوية، إذ يَرسِم تقاطعًا صغيرًا عند موضع كل نقطة. وتُوضِّح الصورة التالية لقطة شاشة من البرنامج أثناء تعديل الإحداثي الأفقي لإحدى النقاط:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95857" href="https://academy.hsoub.com/uploads/monthly_2022_04/004Scatter_Plot.png.ce48aae27ade6e0785f1271164bf4d2c.png" rel=""><img alt="004Scatter_Plot.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95857" data-unique="yhv9kv2hy" src="https://academy.hsoub.com/uploads/monthly_2022_04/004Scatter_Plot.png.ce48aae27ade6e0785f1271164bf4d2c.png" style="width: 450px; height: auto;"></a>
</p>

<p>
	سنحتاج إلى نوع بيانات لتمثيل صفوف الجدول، والذي قد يكون صنفًا بسيطًا يحتوي على خاصيتين <code>x</code> و <code>y</code> لتمثيل إحداثيات النقطة؛ ولكن نظرًا لأننا نريد عمودًا قابلًا للتعديل، فلا نستطيع استخدام خاصياتٍ بسيطة مُعرَّفة بتوابع جَلْب وضبط، وإنما لا بُدّ أن تكون الخاصيات قابلةً للمراقبة. بالتحديد، لا بُدّ أن يَتبِّع الصنف نمط مكتبة JavaFX للخاصيات القابلة للمراقبة والتي تنص على مايلي: ينبغي أن تُخزَّن قيم الخاصيتين <code>x</code> و <code>y</code> بكائنات خاصيات قابلة للمراقبة، كما ينبغي أن يتضمَّن الكائن المُمثِّل للنقطة، وليكن اسمه <code>pt</code>، توابع النسخ <code>pt.xProperty()‎</code> و <code>pt.yProperty()‎</code>؛ إذ تعيد تلك التوابع كائنات الخاصيات القابلة للمراقبة، لكي تُستخدَم بضبط قيم الخاصيات واسترجاعها. وبما أن تلك الخاصيات تُخزِّن قيمًا من النوع <code>double</code>، فإن تلك الكائنات ستكون من النوع <code>DoubleProperty</code>. يُمكِننا تعريف صنف البيانات للجدول على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_43" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Point</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">DoubleProperty</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Point</span><span class="pun">(</span><span class="kwd">double</span><span class="pln"> xVal</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">double</span><span class="pln"> yVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        x </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SimpleDoubleProperty</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="str">"x"</span><span class="pun">,</span><span class="pln">xVal</span><span class="pun">);</span><span class="pln">
        y </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SimpleDoubleProperty</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="str">"y"</span><span class="pun">,</span><span class="pln">yVal</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">DoubleProperty</span><span class="pln"> xProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> x</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">DoubleProperty</span><span class="pln"> yProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> y</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُعَد الصنف <code>DoubleProperty</code> <a href="https://academy.hsoub.com/questions/18997-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D9%83%D9%84%D8%A7%D8%B3-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF-%D9%88%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%9F/?do=getNewComment" rel="">صنفًا مجرَّدًا abstract</a>؛ أما الصنف <code>SimpleDoubleProperty</code>، فهو صنفٌ فرعيٌ حقيقي concrete يتطلَّب بانيه constructor كُلًا من الكائن المُتضمِّن للخاصية واسم الخاصية والقيمة المبدئية لتلك الخاصية؛ وفي المقابل، يُوفِّر الصنف أوتوماتيكيًا مستمعي أحداث التغيير وانعدام الصلاحية invalidation الخاصين بتلك الخاصية.
</p>

<p>
	بعد تعريفنا للصنف <code>Point</code>، يُمكِننا إنشاء الجدول وإضافة بعض النقاط العشوائية إليه على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_45" style="">
<span class="pln">table </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableView</span><span class="pun">&lt;</span><span class="typ">Point</span><span class="pun">&gt;();</span><span class="pln">
points </span><span class="pun">=</span><span class="pln"> table</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">();</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// أضف خمس نقاط عشوائية إلى الجدول</span><span class="pln">
    points</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Point</span><span class="pun">(</span><span class="lit">5</span><span class="pun">*</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">(),</span><span class="pln"> </span><span class="lit">5</span><span class="pun">*</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">())</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عند إضافة نقطة إلى الجدول أو حذف نقطة منه، فلا بُدّ من إعادة رسم الحاوية، ولذلك سنضيف مستمعًا إلى القائمة <code>points</code>، التي تَعمَل مثل نموذج بيانات للجدول:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_47" style="">
<span class="pln">points</span><span class="pun">.</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Observable</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> redrawDisplay</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	لاحِظ تصريحنا عن كون المعامل <code>e</code> بتعبير لامدا السابق lambda expression من النوع <code>Observable</code>؛ وذلك لأن القائمة القابلة للمراقبة تتضمَّن نسختين من التابع <code>addListener()‎</code>، وكلاهما يَستقبِل مُعاملًا واحدًا بتعبير لامدا. وبالتالي يُمكِّن التصريح عن نوع <code>e</code> المُصرِّف من معرفة النسخة التي نريد استدعاءها، فنحن نضيف مستمعًا من النوع <code>InvalidationListener</code> لا من النوع <code>ListChangeListener</code>.
</p>

<p>
	وبذلك نكون قد ضبطنا الحاوية لكي تُعيد رسم نفسها بمجرد إضافة نقطةٍ إلى الجدول أو حذف نقطةٍ منه، ولكننا لم نضبطها بعد لتفعل ذلك عند تعديل إحدى النقاط الموجودة بالجدول؛ لأن ذلك لا يُمثِّل تغييرًا ببنية القائمة، وإنما يُمثِّل تغييرًا ضمن إحدى الكائنات الموجودة بالقائمة. لكي نتمكَّن من الإستجابة لتلك التغييرات أيضًا، يُمكِننا مثلًا إضافة مستمعين إلى الخاصيتين القابلتين للمراقبة المُعرَّفتين بكل كائنٍ من النوع <code>Point</code>. في الواقع، هذا هو ما يفعله الجدول أساسًا لكي يستجيب إلى التغييرات الحادثة بأي نقطة ضمن الجدول، ولكننا لن نتبِع هذا الأسلوب؛ إذ سنضبُط البرنامج بدلًا من ذلك ليستمع إلى نوعٍ آخر من الأحداث، التي ستمكِّنه أيضًا من معالجة تعديلات خلايا الجدول.
</p>

<p>
	يتضمَّن كل جدول خاصيةً قابلةً للمراقبة اسمها <code>editingCell</code>، والتي تحتوي على الخلية التي يُجرَى تعديلها حاليًا أو القيمة الفارغة <code>null</code>، إذا لم تكن هناك أي خليةٍ قيد التعديل. عندما تتغير قيمة تلك الخاصية إلى القيمة الفارغة <code>null</code>، يَعنِي ذلك أن هناك عملية تعديل لخليةٍ ما ضمن الجدول قد اكتملت، وبالتالي سنضبُط الحاوية لكي تعيد رسم نفسها بعد كل عملية تعديل من خلال تسجيل مستمعٍ إلى حدث التغيير بالخاصية <code>editingCell</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_49" style="">
<span class="pln">table</span><span class="pun">.</span><span class="pln">editingCellProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="pln">o</span><span class="pun">,</span><span class="pln">oldVal</span><span class="pun">,</span><span class="pln">newVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newVal </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        redrawDisplay</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	والآن، لكي نُنهِي تعريف الجدول، ينبغي أن نُعرِّف العواميد؛ إذ سنحتاج إلى مصنع قيم خلايا لكل عمود، شرط أن يُنشَأ باستخدام مصنع قيم خاصيات. يتبِّع ذلك نفس النمط الذي اِستخدَمناه بالمثال السابق، ونظرًا لأن العمود هنا قابلٌ للتعديل، فسنحتاج إلى مصنع خلايا أيضًا كما فعلنا تمامًا بمثال القوائم القابلة للتعديل بالأعلى. يُمكِننا إذًا إنشاء مصنع خلايا باستخدام التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_51" style="">
<span class="typ">TextFieldTableCell</span><span class="pun">.</span><span class="pln">forTableColumn</span><span class="pun">(</span><span class="pln">myConverter</span><span class="pun">)</span></pre>

<p>
	إذ أن المعامل <code>myConverter</code> من النوع <code>StringConverter&lt;Double&gt;‎</code>؛ وسيكون من الأفضل بهذا البرنامج لو منعنا المُستخدِم من تغيير حجم الأعمدة أو تغيير ترتيبها. تتضمَّن الشيفرة التالية كل ما هو مطلوب لضبط إحدى العواميد:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5933_53" style="">
<span class="typ">TableColumn</span><span class="pun">&lt;</span><span class="typ">Point</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">&gt;</span><span class="pln"> xColumn </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TableColumn</span><span class="pun">&lt;&gt;(</span><span class="str">"X Coord"</span><span class="pun">);</span><span class="pln">
xColumn</span><span class="pun">.</span><span class="pln">setCellValueFactory</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PropertyValueFactory</span><span class="pun">&lt;</span><span class="typ">Point</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">&gt;(</span><span class="str">"x"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
xColumn</span><span class="pun">.</span><span class="pln">setCellFactory</span><span class="pun">(</span><span class="pln"> </span><span class="typ">TextFieldTableCell</span><span class="pun">.</span><span class="pln">forTableColumn</span><span class="pun">(</span><span class="pln">myConverter</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
xColumn</span><span class="pun">.</span><span class="pln">setSortable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
xColumn</span><span class="pun">.</span><span class="pln">setResizable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
xColumn</span><span class="pun">.</span><span class="pln">setPrefWidth</span><span class="pun">(</span><span class="lit">100</span><span class="pun">);</span><span class="pln">  </span><span class="com">// الحجم الافتراضي صغير للغاية</span><span class="pln">
table</span><span class="pun">.</span><span class="pln">getColumns</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">xColumn</span><span class="pun">);</span></pre>

<p>
	بقي لنا الآن ضبط الجدول ليكون قابلًا للتعديل، وذلك باستدعاء التابع <code>table.setEditable(true)‎</code>. ربما ترى أننا قد اضطررنا لفعل كثيرٍ من الأشياء لمجرد إنشاء جدول خصوصًا إذا كان قابلًا للتعديل؛ ولكن الجداول أكثر تعقيدًا من ذلك بكثير، والشيفرة التي تتطلَّبها مكتبة JavaFX لتهيئة جدول أقل بكثير مما يتطلَّبه تنفيذ جدولٍ من الصفر مباشرةً.
</p>

<p>
	بالمناسبة، عليك أن تنتبه للطريقة التي استخدمنا بها نمط MVC ضمن هذا البرنامج، إذ يُعَد مخطط الانتشار البياني عرضًا view بديلًا لنفس نموذج البيانات المعروض بالجدول؛ كما تُستخدَم البيانات من النموذج عند إعادة رسم الحاوية، ويَحدُث ذلك استجابةً للأحداث النابعة عن أي تعديلات بالنموذج. قد يُفاجئك ذلك، ولكننا لا نحتاج إلى إضافة ما هو أكثر من ذلك لكي نضمَّن استمرار عرض مخطط الانتشار البياني لنفس البيانات على نحو صحيح.
</p>

<p>
	سيكون أيضًا من الأفضل لو ألقيت نظرةً على شيفرة البرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/ScatterPlotTableDemo.java" rel="external nofollow">ScatterPlotTableDemo.java</a>، وستجدها موثقةً جيدًا. بالإضافة إلى فَحْص الصنف <code>TableView</code>؛ كما يُمكِنك كذلك إلقاء نظرةٍ على الطريقة التي اِستخدَمنا بها التحويلات transforms لرسم مخطط الانتشار البياني.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="https://math.hws.edu/javanotes/c13/s3.html" rel="external nofollow">Section 3: Complex Components and MVC</a> من فصل Chapter 13: GUI Programming Continued من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%86-%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D8%A7%D8%AE%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%AC%D8%A7%D9%81%D8%A7-r1526/" rel="">أمثلة عن رسوميات فاخرة باستعمال جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%AE%D8%B7%D9%8A%D8%B7-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1149/" rel="">التخطيط الأساسي لواجهة المستخدم في مكتبة جافا إف إكس JavaFX</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1527</guid><pubDate>Fri, 08 Apr 2022 11:57:32 +0000</pubDate></item><item><title>&#x623;&#x645;&#x62B;&#x644;&#x629; &#x639;&#x646; &#x631;&#x633;&#x648;&#x645;&#x64A;&#x627;&#x62A; &#x641;&#x627;&#x62E;&#x631;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x639;&#x645;&#x627;&#x644; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%86-%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D8%A7%D8%AE%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%AC%D8%A7%D9%81%D8%A7-r1526/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/62500e62b4b60_-.png.555bdaffd27de9401f9820f6e84c9c1e.png" /></p>

<p>
	لقد رأينا أمثلةً كثيرةً على طريقة استخدام كائن سياق رسومي من النوع <code>GraphicsContext</code> للرسم ضمن حاوية، ولكنه يمتلك في الحقيقة ميزات أخرى كثيرة إلى جانب تلك التي تناولناها من قبل. سنناقش خلال هذا القسم رسوم الحاوية، وسنبدأ بفحص تابعين مفيدين لإدارة حالة كائن سياق رسومي.
</p>

<p>
	يمتلك كائنٌ <code>g</code> من النوع <code>GraphicsContext</code> خاصيات متعددة، مثل لون المِلء وعرض الخط، وتؤثر تلك الخاصيات على جميع ما يرسمه الكائن. من المهم أن تتذكر أن أي حاوية تملك كائن سياق رسومي وحيد، وأن أي تغيير يُجرَى على إحدى خاصيات ذلك الكائن، سيُطبَّق على جميع رسومه المُستقبلية إلى أن تتغير قيمة خاصياته مرةً أخرى؛ أي أن تأثير أي تغيير على خاصياته يتجاوز حدود البرنامج الفرعي subroutine الذي نُفَّذت خلاله. ومع ذلك، يحتاج المبرمجون عادةً إلى تغيير قيمة بعض الخاصيات تغييرًا مؤقتًا، بحيث تُعاد إلى قيمها السابقة بعد انتهائهم؛ ولهذا، تتضمَّن واجهة برمجة التطبيقات الخاصة بالرسوميات التابعين <code>g.save()‎</code> و <code>g.restore()‎</code> لتنفيذ ذلك بسهولة.
</p>

<p>
	يُخزِّن التابع <code>g.save()‎</code> عند تنفيذه حالة كائن السياق الرسومي، والتي تتضمَّن جميع الخاصيات التي تؤثر على الرسوم تقريبًا. في الواقع، يَملُك كائن السياق الرسومي <a href="https://wiki.hsoub.com/Algorithms/stacks#:~:text=%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3%20%D9%87%D9%88%20%D9%85%D9%86%20%D8%A8%D9%86%D9%89%20%D8%A7%D9%84%D9%85%D8%B9%D8%B7%D9%8A%D8%A7%D8%AA,%D8%A2%D8%AE%D8%B1%D9%8B%D8%A7%20First%20In%20Last%20Out" rel="external">مكدسًا stack</a>.) للحالات -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3-stack-%D9%88%D8%A7%D9%84%D8%B1%D8%AA%D9%84-queue-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF%D8%A9-adt-r1396/" rel="">المكدس Stack والرتل Queue وأنواع البيانات المجردة ADT</a>-، بحيث يُخزِّن التابع <code>g.save()‎</code> حالة الكائن الحالية بالمكدس؛ وفي المقابل، يَسحَب التابع <code>g.restore()‎</code> عند استدعائه الحالة الموجودة أعلى المكدس، ويَضبُط قيم جميع خاصيات الكائن لتتوافق مع القيم المخزَّنة بالحالة المسحوبة.
</p>

<p>
	نظرًا لاستخدام كائن السياق الرسومي مكدس حالات، فمن الممكن استدعاء التابع <code>save()‎</code> عدة مرات قبل استدعاء التابع <code>restore()‎</code>، ولكن لا بُدّ أن يُقابِل كل استدعاءٍ للتابع <code>save()‎</code> استدعاءً للتابع <code>()restore</code>. ومع ذلك، لا يؤدي استدعاء <code>restore()‎</code> بدون استدعاء سابق مقابل للتابع <code>save()‎</code> إلى حدوث خطأ؛ بل يحدث فقط تجاهُلٌ لتلك الاستدعاءات الإضافية.
</p>

<p>
	يُعدّ استدعاء التابع <code>save()‎</code> ببداية البرنامج الفرعي والتابع <code>restore()‎</code> بنهايته، الطريقة الأسهل عمومًا لضمان عدم تجاوز التغييرات المُجراة على كائن السياق الرسومي ضمن برنامج فرعي معين ما يليه من استدعاءات.
</p>

<p>
	تبرز أهمية حفظ حالة السياق الرسومي واستعادتها لاحقًا أثناء التعامل مع التحويلات transforms، والتي سنناقشها لاحقًا ضمن هذا القسم.
</p>

<h2>
	رسم حواف الأشكال بطريقة فاخرة
</h2>

<p>
	لقد رأينا طريقة رسم حواف الخطوط والمنحنيات وحتى خطوط المحارف النصية، وكذلك طريقة ضبط كُلٍ من لون وحجم الخط المُستخدَم لرسم تلك الحواف strokes. في الواقع، تتوفَّر خاصيات أخرى تؤثر على طريقة رسم الحواف؛ فيُمكِننا مثلًا أن نرسمها بخطوط مُنقطّة أو متقطعة، كما يُمكننا أن نتحكَّم بمظهر نهاية الحواف، وبمظهر نقطة التلاقي بين خيطين أو منحنين، مثل تقابُل جانبي مستطيل بإحدى أركانه. يحتوي كائن السياق الرسومي على خاصيات للتحكُّم بجميع تلك الخاصيات، ولكن من الضروري استخدام خطوطٍ عريضة بما يكفي حتى نتمكَّن من ملاحظة تاثير خاصيات، مثل تلك التي تتحكَّم بمظهر نهاية الحواف ونقط التلاقي. تَعرِض الصورة التالية بعض الخيارات المتاحة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95836" href="https://academy.hsoub.com/uploads/monthly_2022_04/001Line_Attributes.png.64592ba58cb6047d02792b852b82daac.png" rel=""><img alt="001Line_Attributes.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95836" data-unique="yi7zfoxfr" src="https://academy.hsoub.com/uploads/monthly_2022_04/001Line_Attributes.png.64592ba58cb6047d02792b852b82daac.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	تُبيّن نهايتي القطع المستقيمة الثلاثة على يسار الصورة الأنماط الثلاثة المحتملة للخط "cap.‎"، كما ستَجِد الحافة باللون الأسود؛ أما الخط الهندسي، فهو بلون أصفر داخل الحافة. عند استخدام النمط BUTT، تُقطَّع نهايتي الخط الهندسي؛ أما عند استخدام النمط الدائري ROUND، يُضاف قرصٌ بكل نهاية قطره يساوي عرض الخط؛ بينما عند استخدام النمط المربع SQUARE، يُضاف مربعٌ بدلًا من القرص. وما تحصل عليه عند استخدام النمط الدائري أو المربع هو نفس ما تحصل عليه عند رسم حافة بقلم رأسه دائري أو مربع على الترتيب.
</p>

<p>
	إذا كان <code>g</code> كائن سياق رسومي، فسيضبُط التابع <code>g.setLineCap(cap)‎</code> نمط الخط <code>cap</code> المُستخدَم لرسَم الحواف، إذ يَستقبِل التابع معاملًا من نوع التعداد <code>StrokeLineCap</code> المُعرَّف بحزمة <code>javafx.scene.shape</code>، والتي قيمه المحتملة هي <code>StrokeLineCap.BUTT</code> و <code>StrokeLineCap.ROUND</code> والقيمة الافتراضية <code>StrokeLineCap.SQUARE</code>.
</p>

<p>
	تَعمَل نقط التلاقي بنفس الطريقة، إذ يَضبُط التابع <code>g.setLineJoin(join)‎</code> مظهر النقطة التي يلتقي عندها خيطان أو منحنيان، ويَستقبِل التابع في تلك الحالة كائنًا من النوع <code>StrokeLineJoin</code>، وقيمه المحتملة هي القيمة الافتراضية <code>StrokeLineJoin.MITER</code> و <code>StrokeLineJoin.ROUND</code> و <code>StrokeLineJoin.BEVEL</code>؛ إذ تعرِض الصورة السابقة هذه الأنماط الثلاثة بالمنتصف.
</p>

<p>
	عند اِستخدام النمط <code>MITER</code>، تمتد القطعتان المستقيمتان لتكوين نقطة حادة؛ أما عند اِستخدَام النمطين الآخرين، فسيُقطّع الركن. يكون التقطيع بالنسبة للنمط <code>BEVEL</code> باستخدام قطعة مستقيمة؛ أما بالنسبة للنمط <code>ROUND</code>، فسيكون التقطيع باستخدام قوس أو دائرة. تبدو نقط التلاقي الدائرية أفضل إذا رسمت منحنيًا كبيرًا بهيئة سلسلةٍ من القطع المستقيمة القصيرة.
</p>

<p>
	يُمكِننا استخدام التابع <code>g.setLineDashes()‎</code> لتطبيق نمط تقطيع يُظهِر الحواف على نحوٍ مُنقّط أو متقطِّع، إذ تُمثِّل معاملات هذا التابع أطوال القطع والمسافات الفاصلة بينها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_9" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">setLineDashes</span><span class="pun">(</span><span class="pln"> dash1</span><span class="pun">,</span><span class="pln"> gap1</span><span class="pun">,</span><span class="pln"> dash2</span><span class="pun">,</span><span class="pln"> gap2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	لاحِظ أن معاملات التابع هي من النوع <code>double</code>، ويُمكِن تمريرها أيضًا مثل مصفوفةٍ من النوع <code>double[]‎</code>. وإذا رسمنا حافةً معينةً بعد اختيار أيٍّ من أنماط التقطيع، فسيتكوّن الشكل من خطٍ أو منحنًى طوله يُساوِي <code>dash1</code>، متبوعًا بمسافةٍ فارغة طولها <code>gap1</code>، والتي يتبعها خط أو منحنى طوله <code>dash2</code>، وهكذا، وسيُعاد تكرار نفس نمط الخطوط والفراغات بما يكفي لرسم طول الحافة بالكامل.
</p>

<p>
	على سبيل المثال، يرسِم الاستدعاء <code>g.setLineDashes(5,5)‎</code> الحافة مثل متتاليةٍ من القطع القصيرة التي يبلُغ طول كل منها 5 ويَفصِل بينها مسافةً فارغةً طولها يُساوِي 5؛ بينما يَرسِم الاستدعاء <code>g.setLineDashes(10,2)‎</code> متتاليةً من القطع المستقيمة الطويلة، بحيث يَفصِل بينها مسافات قصيرة. يُمكِن تخصيص نمطٍ مُكوَّن من قطعٍ مستقيمة ونقط باستدعاء التابع <code>g.setLineDashes(10,2,2,2)‎</code>، ويتكوَّن النمط المتقطع الافتراضي من خطٍ بدون أي نقاط أو فواصل.
</p>

<p>
	يَسمح البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/StrokeDemo.java" rel="external nofollow">StrokeDemo.java</a> للمُستخدِم برسم خطوط ومستطيلات باستخدام تشكيلةٍ مختلفة من أنماط الخطوط. ألقِ نظرةً على الشيفرة المصدرية لمزيدٍ من التفاصيل.
</p>

<h2>
	تلوين فاخر
</h2>

<p>
	لقد أصبح بإمكاننا رسم حوافٍ فاخرة الآن. ربما لاحظت أن كل عمليات الرسم كانت مقيدةً بلونٍ واحدٍ فقط، ولكن يُمكِننا في الواقع تجاوز ذلك باستخدام الصنف <code>Paint</code>؛ إذ تُستخدَم كائنات هذا الصنف لإسناد لونٍ لكل بكسل نمرُ عليه أثناء الرسم. وفي الواقع، يُعدّ الصنف <code>Paint</code> صنفًا مجرّدًا abstract، وهو مُعرَّف بحزمة <code>javafx.scene.paint</code>. يُعدّ الصنف <code>Color</code> واحدًا فقط من ضمن الأصناف الفرعية الحقيقية المشتقة من الصنف <code>Paint</code>، أي يُمكِننا أن نُمرِّر أي كائن من النوع <code>Paint</code> مثل معاملٍ للتابعين <code>g.setFill()‎</code> و <code>g.setStroke()‎</code>. وعندما يكون الكائن المُمرَّر من النوع <code>Color</code>، سيُطبَّق نفس اللون على جميع البكسلات التي تَمُرّ عبرها عملية الرسم، ولكن هنالك بالطبع أنواع أخرى يَعتمِد فيها اللون المُطبَّق على بكسلٍ معين على إحداثياته.
</p>

<p>
	تُوفِّر <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150" rel="">مكتبة JavaFX</a> عدة أصناف بهذه الخاصية، مثل <code>ImagePattern</code>، ونوعين آخرين للتلوين المُتدرِج؛ فبالنسبة للصنف الأول <code>ImagePattern</code>، يُستخرَج لون البكسل من صورة مكررة -إن اقتضت الضرورة- مثل ورق حائط حتى تُغطِي سطح المستوى xy بالكامل؛ أما بالنسبة للتلوين المُتدرِج، فيتغير اللون المُطبَّق على البكسلات تدريجيًا من لونٍ لآخر بينما ننتقل من نقطة لأخرى. تُوفِّر جافا صنفين من هذا النوع، هما <code>LinearGradient</code> و <code>RadialGradient</code>.
</p>

<p>
	سيكون من المفيد لو اطلعنا على بعض الأمثلة، إذ تعرض الرسمة التالية مضلعًا ملونًا بأسلوبين مختلفين؛ ويَستخدِم المضلع الموجود على اليسار الصنف <code>LinearGradient</code>، بينما يَستخدِم المضلع الموجود على اليمين الصنف <code>ImagePattern</code>. لاحِظ أن اللون قد اُستخدَم هنا لملء المضلع ذاته فقط، أما حواف المُضلع فقد رُسمَت بلونٍ أسود عادي. ومع ذلك، يُمكِننا استخدام كائنات الصنف <code>Paint</code> لرسم حواف الأشكال وملئها أيضًا.
</p>

<p>
	ستَجِد شيفرة رسم المضلعين بالبرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/PaintDemo.java" rel="external nofollow">PaintDemo.java</a>، إذ يُمكِّنك هذا البرنامج من الاختيار بين عدة أنماط تلوين مختلفة، إلى جانب التحكًّم ببعض من خاصيات تلك الأنماط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95837" href="https://academy.hsoub.com/uploads/monthly_2022_04/002Paint_Demo.png.ff9b657c527533eb0d625e2ff362cf21.png" rel=""><img alt="002Paint_Demo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95837" data-unique="zos4nmh1j" src="https://academy.hsoub.com/uploads/monthly_2022_04/002Paint_Demo.png.ff9b657c527533eb0d625e2ff362cf21.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	إذا اخترنا استخدام الصنف <code>ImagePattern</code>، فسنحتاج إلى صورة أولًا. لقد تعرَّضنا للصنف <code>Image</code> بالقسم الفرعي 6.2.3، وتعلمنا طريقة إنشاء كائن من النوع <code>Image</code> من ملف مورد. وبفرض أن <code>pict</code> هو كائنٌ يُمثِّل صورةً من النوع <code>Image</code>، يُمكِننا إنشاء كائنٍ من النوع <code>ImagePattern</code> باستخدام باني الكائن على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_12" style="">
<span class="pln">patternPaint </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ImagePattern</span><span class="pun">(</span><span class="pln"> pict</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> proportional </span><span class="pun">);</span></pre>

<p>
	تُمثِّل المعاملات <code>x</code> و <code>y</code> و <code>width</code> و <code>height</code> قيمًا من النوع <code>double</code>، تتحكَّم بكُلٍ من حجم الصورة وموضعها بالحاوية؛ إذ تُوضَع نسخةٌ واحدةٌ من الصورة بالحاوية، بحيث يقع ركنها الأيسر العلوي بنقطة الإحداثيات (x,y)، وتمتد وفقًا للطول والعرض المُخصَّصين. بعد ذلك، تتكرر الصورة أفقيًا ورأسيًا عدة مرات بما يكفي لملء الحاوية بالكامل، ولكنك ترى فقط الجزء الظاهر عبر الشكل المطلوب تطبيق نمط التلوين عليه.
</p>

<p>
	يُمثِّل المعامل الأخير للباني <code>proportional</code> قيمةً من النوع <code>boolean</code>، وتُخصِّص طريقة تفسير المعاملات الأخرى <code>x</code> و <code>y</code> و <code>width</code> و <code>height</code>؛ فإذا كانت قيمة <code>proportional</code> تُساوِي <code>false</code>، فسيُقاس كُلٌ من <code>width</code> و <code>height</code> باستخدام نظام الإحداثيات المعتاد؛ أما إذا كانت قيمته تساوي <code>true</code>، فسيُقاسان باستخدام مضاعفاتٍ من حجم الشكل المطلوب تطبيق نمط التلوين عليه، وستكون (x,y) مساويةً (0,0) في الركن الأيسر العلوي للشكل (بتعبير أدق، المستطيل المُتضمِّن للشكل). انظر ما يلي على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_14" style="">
<span class="pln">patternPaint </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ImagePattern</span><span class="pun">(</span><span class="pln"> pict</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُنشِيء هذا الباني كائنًا من النوع <code>ImagePattern</code>، بحيث تُغطِي نسخةٌ واحدةٌ من الصورة الشكل بالكامل. وإذا طبقنا نمط التلوين ذاك على عدة أشكال بأحجام مختلفة، فستمتد الصورة بما يتناسب مع كل شكل. وفي المقابل، إذا أردنا أن يحتوي الشكل على أربع نسخ من الصورة أفقيًا ونسختين رأسيًا، فيُمكِننا استخدام ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_16" style="">
<span class="pln">patternPaint </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ImagePattern</span><span class="pun">(</span><span class="pln"> pict</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	في المقابل، يُحدَّد نمط التلوين المُتدرِج الخطي من خلال تخصيص قطعةٍ مستقيمة ولون عدة نقاط على طول تلك القطعة؛ إذ يُطلَق على تلك النقاط وألوانها اسم <strong>وقفات الألوان color stops</strong>، وتُضاف الألوان بينها، بحيث يُسنَد لونٌ معينٌ لكل نقطة على الخط، كما تمتد الألوان عموديًا على القطعة المستقيمة لتنتج شريطًا ملونًا لا نهائي.
</p>

<p>
	ينبغي أيضًا أن نُخصِّص ما يحدث خارج ذلك الشريط، وهو ما يُمكِننا فعله بتخصيص ما يُعرَف باسم "أسلوب التكرار cycle method" <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150/" rel="">بمكتبة JavaFX</a>؛ إذ تشتمل قيمه المحتملة على مايلي:
</p>

<ul>
<li>
		الثابت <code>CycleMethod.REPEAT</code>، الذي يُكرِّر الشريط الملون بما يكفي لتغطية سطح المستوى بالكامل.
	</li>
	<li>
		الثابت <code>CycleMethod.MIRROR</code> الذي يكرِّر أيضًا الشريط الملون، ولكنه يَعكِس كل تكرارٍ منه لتتوافق الألوان الموجودة على أطراف كل تكرار مع بعضها.
	</li>
	<li>
		الثابت <code>CycleMethod.NO_REPEAT</code>، الذي يَمِدّ اللون الموجود على كل طرف لا نهائيًا.
	</li>
</ul>
<p>
	تَعرِض الصورة التالية ثلاثة أنماط تلوين مُتدرِج تستخدِم جميعها نفس خاصيات القطعة المستقيمة ووقفات الألوان، إذ تَستخدِم تلك الموجودة على يسار الصورة أسلوب التكرار <code>MIRROR</code>؛ بينما تَستخدِم الموجودة بمنتصف الصورة أسلوب التكرار <code>REPEAT</code>؛ في حين تَستخدِم تلك الموجودة على اليمين أسلوب التكرار <code>NO_REPEAT</code>.
</p>

<p>
	رسمنا القطعة المستقيمة ووضعنا علامات على مواضع وقفات الألوان على طول تلك القطعة، كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95838" href="https://academy.hsoub.com/uploads/monthly_2022_04/003Linear_Gradient.png.f5f2d2bf121a6d3bd70ad920b4330bff.png" rel=""><img alt="003Linear_Gradient.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95838" data-unique="zv6wrjytg" src="https://academy.hsoub.com/uploads/monthly_2022_04/003Linear_Gradient.png.f5f2d2bf121a6d3bd70ad920b4330bff.png" style="width: 380px; height: auto;"></a>
</p>

<p>
	يُنشِئ الباني التالي نمط تلوين متدرج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_23" style="">
<span class="pln">linearGradient </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinearGradient</span><span class="pun">(</span><span class="pln"> x1</span><span class="pun">,</span><span class="pln">y1</span><span class="pun">,</span><span class="pln"> x2</span><span class="pun">,</span><span class="pln">y2</span><span class="pun">,</span><span class="pln"> proportional</span><span class="pun">,</span><span class="pln"> cycleMethod</span><span class="pun">,</span><span class="pln">
                                                stop1</span><span class="pun">,</span><span class="pln"> stop2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	تُمثِّل المعاملات الأربعة الأولى قيمًا من النوع <code>double</code>، إذ تُخصِّص نقطتي البداية والنهاية للقطعة المستقيمة (x1,y1) و (x2,y2)؛ أما المعامل الخامس <code>proportional</code>، فهو من النوع <code>boolean</code>؛ فإذا كانت قيمته تساوي <code>false</code>، فستُفسَّر نقطتي البداية والنهاية باستخدام نظام الإحداثيات المعتاد؛ أما إذا كانت قيمته تساوي <code>true</code>، فإنها تُفسَّر باستخدام نظام إحداثيات تقع نقطته (0,0) في الركن الأيسر العلوي للشكل المطلوب تطبيق نمط التلوين عليه، بينما تقع نقطته (1,1) في الركن الأيمن السفلي لنفس الشكل.
</p>

<p>
	يُمثِّل المعامل السادس <code>cycleMethod</code> إحدى الثوابت <code>CycleMethod.REPEAT</code> و <code>CycleMethod.MIRROR</code> و <code>CycleMethod.NO_REPEAT</code>؛ بينما تشير المعاملات المتبقية إلى وقفات الألوان، ويُمثَل كل وقفةٍ منها كائنًا من النوع <code>Stop</code>.
</p>

<p>
	يستقبل باني الصنف <code>Stop</code> معاملين من النوع <code>double</code> و <code>Color</code>؛ إذ يُخصِّص المعامل الأول مكان الوقفة على طول القطعة المستقيمة، وتكون قيمته نسبةً من المسافة بين نقطتي البداية والنهاية. في العموم، لا بُدّ أن يكون مكان الوقفة الأولى عند <code>0</code> ومكان الوقفة الأخيرة عند <code>1</code>؛ كما لا بُدّ أن تكون قيمة مكان كل وقفة أكبر من قيمة مكان الوقفة التي تَسبِقها، إذ يُمكِننا مثلًا إنشاء نمط التلوين المُتدرِج المُستخدَم لتلوين الشكل الموجود على يسار الصورة السابقة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_25" style="">
<span class="pln">grad </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinearGradient</span><span class="pun">(</span><span class="pln"> </span><span class="lit">120</span><span class="pun">,</span><span class="lit">120</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="lit">180</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CycleMethod</span><span class="pun">.</span><span class="pln">MIRROR</span><span class="pun">,</span><span class="pln">
                                  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Stop</span><span class="pun">(</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">   </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.3</span><span class="pun">)</span><span class="pln"> </span><span class="pun">),</span><span class="pln"> 
                                  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Stop</span><span class="pun">(</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="lit">0.3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">),</span><span class="pln"> 
                                  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Stop</span><span class="pun">(</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">   </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.3</span><span class="pun">)</span><span class="pln">   </span><span class="pun">)</span><span class="pln">  </span><span class="pun">);</span></pre>

<p>
	بالنسبة لنمط التلوين المُتدرِج الخطي، سيكون اللون ثابتًا على طول عدة خطوط معينة؛ أما بالنسبة لنمط التلوين الدائري، فسيكون اللون ثابتًا بالنسبة لدوائر معينة؛ إذ تُخصَّص وقفات الألوان على طول نصف قطر دائرة بأبسط حالات التلوين المتدرج الدائري، ويكون اللون ثابتًا للدوائر التي تتشارك نفس المركز. وبالمثل في نمط التلوين الخطي، إذ يُحدِّد أسلوب التكرار اللون المُستخدَم خارج الدائرة. في الحقيقة، الأمر أكثر تعقيدًا من ذلك، فقد يتضمن نمط التلوين الدائري أيضًا نقطةً محورية، والتي تكون هي نفسها مركز الدائرة في الحالة الأبسط، ولكنها في العموم قد تكون أي نقطة أخرى داخل الدائرة.
</p>

<p>
	تحدِّد وقفة اللون بالموضع 0 اللون المُستخدَم عند النقطة المحورية، بينما تُحدِّد وقفة اللون بالموضع 1 اللون المُستخدَم على طول الدائرة، وتَستخدِم جميع الرسوم بالصورة التالية نفس نمط التلوين المُتدرِّج، ولكنها تختلف بمكان النقطة المحورية. ستلاحظ وجود علامتين بالرسوم الموجودة بالصف الأول من الصورة، إذ تُمثِّل العلامتان الدائرة والنقطة المحورية. لاحِظ أن اللون المُستخدَم عند النقطة المحورية هو الأحمر، بينما اللون المُستخدَم على طول الدائرة هو الأصفر:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95839" href="https://academy.hsoub.com/uploads/monthly_2022_04/004Radial_Gradient.jpg.eb8ef098673cabe1e267ffa38e04776f.jpg" rel=""><img alt="004Radial_Gradient.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="95839" data-unique="ckf2zguaw" src="https://academy.hsoub.com/uploads/monthly_2022_04/004Radial_Gradient.jpg.eb8ef098673cabe1e267ffa38e04776f.jpg" style="width: 400px; height: auto;"></a>
</p>

<p>
	يَستقبِل باني الكائن كثيرًا من المعاملات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_29" style="">
<span class="pln">radialGradient </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RadialGradient</span><span class="pun">(</span><span class="pln"> focalAngle</span><span class="pun">,</span><span class="pln">focalDistance</span><span class="pun">,</span><span class="pln">
                            centerX</span><span class="pun">,</span><span class="pln">centerY</span><span class="pun">,</span><span class="pln">radius</span><span class="pun">,</span><span class="pln">
                            proportional</span><span class="pun">,</span><span class="pln"> cycleMethod</span><span class="pun">,</span><span class="pln">
                            stop1</span><span class="pun">,</span><span class="pln"> stop2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُخصِّص المعاملان الأول والثاني موضع النقطة المحورية، إذ يشير <code>focalDistance</code> إلى المسافة التي تبعدها النقطة المحورية عن مركز الدائرة، وتُخصَّص على أنها نسبةٌ من نصف القطر، أي لا بُدّ أن تكون أصغر من 1؛ بينما يشير <code>focalAngle</code> إلى الاتجاه الذي تتحرك إليه النقطة المحورية بعيدًا عن المركز.
</p>

<p>
	في الحالة الأبسط من نمط التلوين المتدرج الدائري، تكون المسافة مساويةً للصفر ولا يكون للاتجاه أي معنًى. تُخصِّص المعاملات الثلاثة التالية مركز الدائرة وطول نصف قطرها؛ في حين تعمل المعاملات المتبقية على نحوٍ مشابه للمعاملات المُستخدَمة بنمط التلوين المتدرج الخطي.
</p>

<h2>
	التحويلات Transforms
</h2>

<p>
	تشير النقطة (0,0) تبعًا لنظام الإحداثيات القياسي الخاص بمكون الحاوية إلى ركنها الأيسر العلوي؛ بينما تشير النقطة (x,y) إلى تلك النقطة التي تبعد مسافة طولها x بكسل من الجانب الأيسر للحاوية ومسافة y بكسل من جانبها العلوي، ومع ذلك ليس هناك أي تقييد بخصوص ضرورة استخدام نظام الاحداثيات ذاك، ففي الحقيقة، يُمكِننا أن نضبُط كائن السياق الرسومي لكي يَستخدِم أنظمة إحداثيات أخرى تعتمد على وحدات طول مختلفة، بل ومحاور إحداثيات مختلفة. والهدف من ذلك هو اختيار نظام الإحداثيات الذي يتناسب أكثر مع ما نرغب برسمه. على سبيل المثال، إذا كنا نرسم مخططات معمارية، يُمكِننا عندها استخدام إحداثيات يُمثِّل طول كل وحدةٍ منها القيمة الفعلية لواحد قدم.
</p>

<p>
	يُطلَق اسم "التحويلات transforms" على التغييرات الحادثة بنظام الإحداثيات. وهناك ثلاثة أنواع بسيطة من التحويلات يمكن توضيحها في الآتي:
</p>

<ul>
<li>
		أولًا، يُعدِّل الانتقال translate موضع نقطة الأصل (0,0).
	</li>
	<li>
		ثانيًا، يُعدِّل التحجيم scale المقياس المُستخدَم أي وحدة المسافة.
	</li>
	<li>
		ثالثًا، يُطبِّق الدوران rotation دورانًا حول نقطة.
	</li>
</ul>
<p>
	تتوفَّر تحويلات أخرى أقل شيوعًا، مثل الإمالة shear التي تُميِل الصورة بعض الشيء. تعرِض الرسمة التوضيحية التالية نفس الصورة بعد إجراء تحويلات مختلفة عليها:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95840" href="https://academy.hsoub.com/uploads/monthly_2022_04/005Transforms.png.315e28e344093b9b28f7c4dc80df8fc8.png" rel=""><img alt="005Transforms.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95840" data-unique="pctowz7mf" src="https://academy.hsoub.com/uploads/monthly_2022_04/005Transforms.png.315e28e344093b9b28f7c4dc80df8fc8.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	لاحظِ أن كل محتويات الصورة بما في ذلك النصوص، قد تأثرت بالتحويلات المُجراة.
</p>

<p>
	يُمكِننا أن نُجرِي تحويلًا أكثر تعقيدًا بدمج مجموعةٍ من تلك التحويلات الثلاثة البسيطة، إذ يمكننا مثلًا، تطبيق دورانٍ متبوعٍ بتحجيم، ثم بانتقال، ثم بدورانٍ مرةً أخرى. عندما نُطبِّق عدة تحويلات بالتتابع سيتراكم تأثيرها، ولهذا يكون من الصعب عادةً فهم تأثير التحويلات المعقدة فهمًا كاملًا.
</p>

<p>
	تُعدّ التحويلات عمومًا موضوعًا ضخمًا يُمكِن تغطيته بدورة عن الرسوم الحاسوبية computer graphics، ولكننا نناقش هنا فقط بعض الحالات البسيطة لكي نحظى بفكرةٍ عما يُمكِن لتلك التحويلات أن تفعله.
</p>

<p>
	يُعدّ التحويل الحالي خاصيةً مُعرَّفةً بكائن السياق الرسومي؛ فهو يُمثِّل جزءًا من الحالة التي يُخزِّنها التابع <code>save()‎</code> ويستعيدها التابع <code>restore()‎</code>. من المهم جدًا اِستخدَام التابعين <code>save()‎</code> و <code>restore()‎</code> عند التعامل مع التحويلات؛ لكي نمنع تأثير التحويلات التي يجريها برنامجٌ فرعيٌ معين على ما يتبعه من استدعاءات لبرامج فرعية أخرى. وبالمثل من بقية الخاصيات الأخرى المُعرَّفة بكائن السياق الرسومي، يمتد تأثير التحويلات على الأشياء المرسومة لاحقًا بعد تطبيق التحويلات على كائن السياق الرسومي.
</p>

<p>
	لنفترض أن <code>g</code> كائن سياق رسومي من النوع <code>GraphicsContext</code>، عندها يُمكِننا أن نُطبِّق انتقالًا على <code>g</code> باستدعاء <code>g.translate(x,y)‎</code>؛ إذ تمثِّل <code>x</code> و <code>y</code> قيمًا من النوع <code>double</code>، ويتلخص تأثيرها حسابيًا في إضافة (x,y) على الإحداثيات بعمليات الرسم التالية. فإذا استخدمنا مثلًا النقطة (0,0) بعد تطبيق هذا الانتقال، فإننا فعليًا نشير إلى النقطة التي إحداثياتها تساوي (x,y) وفقَا لنظام الإحداثيات القياسي، ويَعنِي ذلك أن جميع أزواج الإحداثيات قد تحركت بمقدارٍ معين. ألقِ نظرةً على التعليمتين التاليتين:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_32" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
g</span><span class="pun">.</span><span class="pln">strokeLine</span><span class="pun">(</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	ترسم التعليمتان السابقتان نفس الخط الذي ترسمه التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_34" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">strokeLine</span><span class="pun">(</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">+</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">+</span><span class="pln">y </span><span class="pun">);</span></pre>

<p>
	تؤدي النسخة الثانية من الشيفرة نفس عملية الانتقال ولكن يدويًا، وبدلًا من محاولة التفكير بعملية الانتقال باستخدام أنظمة الإحداثيات، قد يكون من الأسهل لنا لو فكرنا بما يَحدُث للأشكال التي ستُرسَم لاحقًا، فمثلًا، بعد استدعاء التابع <code>g.translate(x,y)‎</code>، ستتحرك جميع الكائنات التي نرسمها بمسافة x من الوحدات أفقيًا وبمسافة y من الوحدات رأسيًا.
</p>

<p>
	وفي مثال آخر، قد يُفضِّل البعض أن تُمثِّل النقطة (0,0) منتصف مكون الحاوية بدلًا من ركنها الأيسر العلوي، ويُمكِننا ذلك باستدعاء الأمر التالي قبل رسم أي شيء:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_36" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">()/</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">()/</span><span class="lit">2</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُمكِننا أن نُطبِّق تحجيمًا على <code>g</code> باستدعاء التابع <code>g.scale(sx,sy)‎</code>، إذ تشير المعاملات إلى معامل التحجيم بالاتجاهين x و y. وبعد تنفيذ هذا الأمر، ستُضرَّب إحداثيات x بمقدار يُساوِي <code>sx</code>؛ في حين تُضرَّب إحداثيات y بمقدار يُساوِي <code>sy</code>، ويكون تأثير ذلك على الأشياء المرسومة هو بجعلها أكبر أو أصغر. بالتحديد، تؤدي معاملات التحجيم التي تتجاوز قيمتها العدد 1 إلى تكبير حجم الأشكال؛ في حين تؤدي معاملات التحجيم التي قيمتها أقل من 1 إلى تصغير حجمها. وتُستخدَم عادةً نفس قيمة معامل التحجيم للمحورين، ويُطلَق عليه في تلك الحالة اسم "التحجيم المنتظم uniform scaling".
</p>

<p>
	تُعدّ النقطة (0,0) مركز التحجيم، أي النقطة التي لا تتأثر بعملية التحجيم؛ بينما تنتقل النقاط الأخرى باتجاه أو بعكس اتجاه النقطة (0,0)، وإذا لم يكن الشكل موجودًا بالنقطة (0,0)، فلا يقتصر تأثير التحجيم على تغيير حجم الشكل، وإنما نقله أيضًا بعيدًا عن النقطة (0,0) لمعاملات التحجيم الأكبر من 1 وقريبًا من النقطة (0,0) لمعاملات التحجيم الأقل من 1.
</p>

<p>
	يُمكِننا أيضًا أن نَستخدِم معاملات تحجيم سالبة، والتي يَنتُج عنها حدوث انعكاس، إذ تنعكس الأشكال أفقيًا مثلًا حول الخط x=0، وذلك بعد استدعاء التابع <code>g.scale(-1,1)‎</code>.
</p>

<p>
	يُعدّ الدوران هو النوع الثالث من التحويلات البسيطة، إذ يتسبَّب استدعاء التابع <code>g.rotate(r)‎</code> بدوران جميع الأشكال التي نرسمها لاحقًا بزاوية <code>r</code> حول النقطة (0,0). تُقاس الزوايا بوحدة الدرجات، وتُمثِّل الزوايا الموجبة دورانًا باتجاه عقارب الساعة؛ بينما تُمثِل الزوايا السالبة دورانًا بعكس اتجاه عقارب الساعة، إلا لو كنا قد طبقنا مُسبقًا معامل تحجيم سالب، إذ يؤدي إلى عَكْس الإتجاه.
</p>

<p>
	لا تُعدّ الإمالة shearing عملية تحويل بسيطة، وذلك لأنه من الممكن تنفيذها (مع بعض الصعوبة) عبر متتالية من عمليات الدوران والتحجيم؛ إذ يتمثَّل تأثير الإمالة الأفقية بنقل الخطوط الأفقية إلى اليسار أو إلى اليمين بمقدار يتناسب مع المسافة من المحور الأفقي x، فتنتقل النقطة (x,y) إلى النقطة (x+a*y,y)، لأن <code>a</code> هو مقدار الإمالة ذاك.
</p>

<p>
	لا تتضمّن مكتبة JavaFX تابعًا لتطبيق عملية الإمالة، ولكنها تملُك طريقةً لتطبيق عملية تحويل عشوائي. إذا كان لديك معرفةٌ بالجبر الخطي linear algebra، فلربما تعرف أن المصفوفات تُستخدَم لتمثيل التحويلات، وأنه من الممكن تخصيص أي عملية تحويل بتحديد الأعداد الموجودة بالمصفوفة مباشرةً.
</p>

<p>
	تَستخدِم مكتبة JavaFX كائنات من النوع <code>Affine</code> لتمثيل التحويلات، ويُطبِّق التابع <code>g.transform(t)‎</code> تحويلًا <code>t</code> من النوع <code>Affine</code> على كائن السياق الرسومي. لن نتطرّق للرياضيات هنا، ولكن تُجرِي الشيفرة التالية عملية إمالة أفقية بمقدار يساوي <code>a</code>:
</p>

<pre class="ipsCode">
g.transform( new Affine(1, a, 0, 0, 1, 0) );
</pre>

<p>
	قد نحتاج في بعض الأحيان إلى تطبيق عدة تحويلات للحصول على التأثير المطلوب. لنفترض مثلًا أننا نريد أن نعرض كلمة "hello world" بعد إمالتها بزاوية 30 درجة، بحيث تقع نقطتها الأصلية بالنقطة (x,y). في الواقع، لن تتمكَّن الشيفرة التالية من تنفيذ ذلك:
</p>

<pre class="ipsCode">
g.rotate(-30);
g.fillText("hello world", x, y);
</pre>

<p>
	يكمُن سبب المشكلة في أن عملية الدوران تُطبَّق على كُلٍ من النقطة (x,y) والنص، وبالتالي لا تظِلّ النقطة الأصلية بالموضع (x,y) بعد تطبيق عملية الدوران؛ فكل ما نحتاج إليه هو أن نُنشِئ سلسلةً نصيةً مدارةً بنقطة أصلية واقعة عند (0,0)، ثم يُمكِننا أن ننقلها مسافة (x,y)، وهو ما سيؤدي إلى تحريك النقطة الأصلية من (0,0) إلى (x,y). تُنفِّذ الشيفرة التالية ذلك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_38" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
g</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(-</span><span class="lit">30</span><span class="pun">);</span><span class="pln">
g</span><span class="pun">.</span><span class="pln">fillText</span><span class="pun">(</span><span class="str">"hello world"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	ما ينبغي ملاحظته هنا هو الترتيب الذي تُطبَّق به عمليات التحويل؛ إذ تُطبَّق عملية الانتقال على جميع الأشياء التي تلي الأمر <code>translate</code>، والتي هي ببساطة بعض الشيفرة المسؤولة عن رسم سلسلة نصية مدارة بنقطة أصلية واقعة عند (0,0). بعد ذلك، تنتقل تلك السلسلة النصية المدارة بمسافة قدرها (x,y). يَعنِي ذلك أن السلسلة النصية تُدار أولًا ثم تُنقَل. يُمكِننا أن نُلخِص ذلك في أن عمليات الانتقال تُطبَّق بترتيبٍ معاكس لترتيب ظهور أوامر الانتقال بالشيفرة.
</p>

<p>
	بإمكان البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/TransformDemo.java" rel="external nofollow">TransformDemo.java</a> تطبيق تشكيلةٍ مختلفة من التحويلات على صورة، كما يُمكِّن المُستخدِم من التحكُّم بمقدار كُلٍ من عمليات التحجيم والإمالة الأفقية والدوران والانتقال. قد يساعدك تشغيل البرنامج على فهم التحويلات أكثر، كما يُمكِّنك من فحص الشيفرة المصدرية للبرنامج لترى طريقة تنفيذ تلك العمليات.
</p>

<p>
	تُطبَّق التحويلات ضمن هذا البرنامج وفقًا للترتيب التالي: تحجيم، ثم إمالة، ثم دوران، ثم إنتقال. وإذا فحصت الشيفرة، سترى أن التوابع المسؤولة عن عمليات التحويل مُستدعاة بالترتيب المعاكس: انتقال، ثم دوران، ثم إمالة، ثم تحجيم. هنالك أيضًا عملية انتقال إضافية لتحريك نقطة الأصل إلى مركز الحاوية لكي يصبح مركز عمليات التحجيم والدوران والإمالة هو منتصف الحاوية.
</p>

<h2>
	الحاويات المكدسة Stacked
</h2>

<p>
	يُعدّ برنامج الرسم البسيط <a href="https://math.hws.edu/javanotes/source/chapter13/ToolPaint.java" rel="external nofollow">ToolPaint.java</a> المثال الأخير الذي سنتعرض له بهذا القسم. كنا قد تعرضنا لبرامج رسم في جزئيات سابقة من هذه السلسلة، ولكنها كانت مقتصرةً على رسم المنحنيات فقط، ولكن سيُمكِّن البرنامج الجديد المُستخدِم من اختيار أداةٍ للرسم، إضافةً إلى أداة رسم المنحنيات؛ إذ سيتضمَّن البرنامج أدوات لرسم خمسة أنواع من الأشكال موضحة في الآتي:
</p>

<ul>
<li>
		خطوط مستقيمة.
	</li>
	<li>
		مستطيلات.
	</li>
	<li>
		أشكال بيضاوية .
	</li>
	<li>
		مستطيلات ملونة.
	</li>
	<li>
		أشكال بيضاوية ملونة.
	</li>
</ul>
<p>
	عندما يَرسِم المُستخدِم شكلًا بأي من أدوات الرسم الخمسة، فعليه سحَب الفأرة ورسم الشكل من النقطة التي بدأت عندها عملية السحب إلى الموضع الحالي للفأرة. وبكل مرة تتحرك خلالها الفأرة، يُحذَف الشكل السابق ويُرسَم شكلٌ جديد. بالنسبة لأداة رسم الخط مثلًا، يكون التأثير هو رؤية خطٍ مُمتدٍ من نقطة بدء عملية السحب إلى الموضع الحالي لمؤشر الفأرة، ويتحرك هذا الخط مع حركة الفأرة. سيكون من الأفضل لو شغّلت البرنامج وجرَّبت هذا التأثير بنفسك.
</p>

<p>
	تَكْمُن صعوبة برمجة ذلك في أن بعض أجزاء الرسمة تُغطَّى وتُكشَف باستمرار، بينما يُحرِّك المُستخدِم مؤشر الفأرة؛ وعندما يُغطَّى جزءٌ من الرسمة الحالية ثم يُكشَف عنه مرةً أخرى، فلا بُدّ أن تَظلّ الرسمة ظاهرة. ويَعنِي ذلك أن البرنامج لا يستطيع أن يرسم الشكل على نفس الحاوية المُتضمِّنة للرسمة؛ لأن ذلك سيَمحِي ما كان موجودًا بذلك الجزء من الرسمة.
</p>

<p>
	يتلخص الحل بمكتبة JavaFX باستخدام مكوني حاوية واحدًا فوق الآخر؛ بحيث يحتوي مكون الحاوية السفلي على الرسمة الفعلية؛ بينما يُستخدَم مكون الحاوية العلوي لتنفيذ أدوات رسم الأشكال. ينبغي أن تكون الحاوية العلوية شفافة، أي أن تُملأ بلونٍ درجة شفافيته تُساوِي صفر. تُرسَم الأشكال بالحاوية العلوية بينما يسَحب المُستخدِم مؤشر الفأرة بعد اختياره لأداة رسم معينة، وبالتالي لا تتأثر الحاوية السفلية مع إمكانية اختفاء بعض أجزائها خلف الشكل المرسوم بالحاوية العلوية.
</p>

<p>
	في كل مرة يتحرك خلالها مؤشر الفأرة، ستُحذَف مكونات الحاوية العلوية وسيُعاد رسم الشكل الجديد بها؛ وعندما يُحرِر المُستخدِم زر الفأرة بنهاية عملية السحب، ستُحذَف مكونات الحاوية العلوية وسيُرسَم الشكل هذه المرة بالحاوية السفلية ليُصبِح جزءًا من الرسمة الفعلية، وبالتالي تُصبِح الحاوية العلوية شفافةً مجددًا بينما تُصبِح مكونات الحاوية السفلية مرئيةً بالكامل. يُمكِننا حذف محتويات حاوية لتصبح بعدها شفافةً تمامًا باستدعاء التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_40" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">clearRect</span><span class="pun">(</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">(),</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذ أن <code>g</code> هو كائن السياق الرسومي من النوع <code>GraphicsContext</code> المقابل للحاوية، وتكون الحاويات شفافةً بالكامل بمجرد إنشائها.
</p>

<p>
	يُمكننا استخدام كائنٍ من النوع <code>StackPane</code> لوضع حاويةٍ فوق حاوية أخرى، إذ تُرتِّب كائنات الصنف <code>StackPane</code> عُقدها nodes الأبناء بعضها فوق بعض بنفس ترتيب إضافتها إليها؛ وعليه، يُمكِننا إنشاء الحاويتين المُستخدمتين بهذا البرنامج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_42" style="">
<span class="pln">canvas </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Canvas</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln">height</span><span class="pun">);</span><span class="pln"> </span><span class="com">// حاوية الرسم الرئيسية</span><span class="pln">
canvasGraphics </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getGraphicsContext2D</span><span class="pun">();</span><span class="pln">
canvasGraphics</span><span class="pun">.</span><span class="pln">setFill</span><span class="pun">(</span><span class="pln">backgroundColor</span><span class="pun">);</span><span class="pln">
canvasGraphics</span><span class="pun">.</span><span class="pln">fillRect</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="pln">width</span><span class="pun">,</span><span class="pln">height</span><span class="pun">);</span><span class="pln">

overlay </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Canvas</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln">height</span><span class="pun">);</span><span class="pln"> </span><span class="com">// الحاوية الشفافة العلوية</span><span class="pln">
overlayGraphics </span><span class="pun">=</span><span class="pln"> overlay</span><span class="pun">.</span><span class="pln">getGraphicsContext2D</span><span class="pun">();</span><span class="pln">
overlay</span><span class="pun">.</span><span class="pln">setOnMousePressed</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> mousePressed</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
overlay</span><span class="pun">.</span><span class="pln">setOnMouseDragged</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> mouseDragged</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
overlay</span><span class="pun">.</span><span class="pln">setOnMouseReleased</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> mouseReleased</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

</span><span class="typ">StackPane</span><span class="pln"> canvasHolder </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">StackPane</span><span class="pun">(</span><span class="pln">canvas</span><span class="pun">,</span><span class="pln">overlay</span><span class="pun">);</span></pre>

<p>
	ملاحظة: أُضيفت معالجات أحداث الفأرة إلى الحاوية العلوية لا السفلية، لأنها تُغطِي الحاوية السفلية؛ فعندما يَنقُر المُستخدِم على الرسمة الموجودة بالحاوية السفلية، فإنه فعليًا ينقر على الحاوية العلوية.
</p>

<p>
	بالمناسبة، لا تَستخدِم أداة رسم المنحنيات الحاوية العلوية تحديدًا، وإنما تُرسَم المنحنيات مباشرةً بالحاوية السفلية. ونظرًا لأن المُستخدِم لا يستطيع حذف أي جزء من المنحنى بعد رسمه، لا حاجة لوضع نسخةٍ مؤقتةٍ منه بالحاوية العلوية.
</p>

<h2>
	العمليات على البكسلات
</h2>

<p>
	يتضمَّن البرنامج <a href="https://math.hws.edu/javanotes/source/chapter13/ToolPaint.java" rel="external nofollow">ToolPaint.java</a> أداتين إضافيتين، هما "الحذف Erase" و "التلطيخ Smudge"، إذ تَعمَل كلتاهما بالحاوية السفلية مباشرةً. تَعمَل أداة الحذف على النحو التالي: بينما يَسحَب المُستخدِم مؤشر الفأرة بعد اختيار تلك الأداة، يُملأ مربعٌ صغير حول موضع المؤشر باللون الأسود لكي يحذف جزء الرسمة الموجود بهذا الموضع. وفي المقابل، تَعمَل أداة التلطيخ على النحو التالي: يؤدي السَحْب بعد اختيار تلك الأداة إلى تلطيخ اللون أسفل الأداة، تمامًا كما لو سحبت إصبعك عبر لون مبلل.
</p>

<p>
	تُبيِّن الصورة التالية لقطة شاشة من البرنامج بعد سَحْب أداة التلطيخ حول مركز مستطيل أحمر:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95841" href="https://academy.hsoub.com/uploads/monthly_2022_04/006Smudge_Rect.png.5fd65b3e6aee87567cd77bfca134ac84.png" rel=""><img alt="006Smudge_Rect.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95841" data-unique="bgn74d8qw" src="https://academy.hsoub.com/uploads/monthly_2022_04/006Smudge_Rect.png.5fd65b3e6aee87567cd77bfca134ac84.png"></a>
</p>

<p>
	لا تتضمَّن مكتبة JavaFX برنامجًا فرعيًا مبنيًا مُسبقًا لإجراء تلطيخ على صورة، فهو أمرٌ يتطلّب معالجةً مباشرةً لألوان البكسلات كُلٌ على حدى. تتلخص الفكرة الأساسية لتلك الأداة فيما يلي: يَستخدِم البرنامج ثلاث مصفوفات ثنائية البعد بحجم 9 x‏ 9، واحدةٌ لكل مُكوِّن لون؛ أي واحدةٌ تَحمِل المكون الأحمر؛ وواحدة للمكون الأخضر؛ والأخيرة للمكون الأزرق.
</p>

<p>
	عندما ينقر المُستخدِم على الفأرة أثناء استخدام أداة التلطيخ، تُنسَخ مكونات لون البكسلات الموجودة داخل المربع المحيط بمؤشر الفأرة من الصورة إلى المصفوفات الثلاثة، وعندما يُحرِّك المُستخدِم الفأرة، تمتزج بعضًا من ألوانها بألوان البكسلات الموجودة بالمكان الجديد لمؤشر الفأرة. وبنفس الوقت، يُنسَخ بعضٌ من ألوان الصورة إلى المصفوفات، أي أن المصفوفات تُسقِط بعضًا من الألوان التي تحملها وتلتقط بعض الألوان من المكان الجديد. إذا فكرت بالأمر قليلًا، سترى أن ذلك يشبه تمامًا ما يحدث عندما تُمرِّر إصبعك بدهانٍ حديث.
</p>

<p>
	ينبغي إذًا أن نتمكَّن من قراءة ألوان البكسلات الموجودة بالصورة لكي نُنفِّذ تلك العملية، كما علينا أن نكون قادرين على كتابة ألوان جديدة بتلك البكسلات. في الواقع، من السهل كتابة الألوان بالبكسلات باستخدام الصنف <code>PixelWriter</code>؛ فإذا كان <code>g</code> كائن سياق رسومي من النوع <code>GraphicsContext</code>، وكان هذا الكائن مُرتبطًا بمكون حاوية، عندها يُمكِننا إنشاء كائنٍ من النوع <code>PixelWriter</code> لتلك الحاوية باستدعاء ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_44" style="">
<span class="typ">PixelWriter</span><span class="pln"> pixWriter </span><span class="pun">=</span><span class="pln"> g</span><span class="pun">.</span><span class="pln">getPixelWriter</span><span class="pun">();</span></pre>

<p>
	ولكي نتمكَّن من ضبط لون البكسل الموجود بالنقطة (x,y) من الحاوية، يُمكِننا استدعاء التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_46" style="">
<span class="pln">pixWriter</span><span class="pun">.</span><span class="pln">setColor</span><span class="pun">(</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color </span><span class="pun">);</span></pre>

<p>
	إذ أن <code>color</code> هو كائنٌ من النوع <code>Color</code>، وتمثِّل x و y إحداثيات بكسل، وهم ليسوا عُرضَةً لأي عملية تحويل قد تُطبَّق على كائن السياق الرسومي.
</p>

<p>
	لو كانت مكتبة JavaFX توفِّر طريقةً سهلةً لقراءة ألوان البكسلات من الحاوية، فسنكون قد انتهينا فعلًا، ولكن لسوء الحظ، ليس الأمر بهذه البساطة؛ إذ يبدو أن عمليات الرسم لا تُطبَّق على الحاوية على الفور، وإنما تُخزَّن مجموعةٌ من عمليات الرسم، ثم تُرسَل إلى عتاد جهاز الرسوم دفعةً واحدة، وذلك لرفع الكفاءة. بالتحديد، تُرسَل مجموعة العمليات فقط عندما تُصبِح عملية إعادة رسم الحاوية على الشاشة ضرورية. ويَعنِي ذلك، أننا لو قرأنا لون بكسل من الحاوية، فإننا لا نَضمَن أن تكون القيمة التي نحصل عليها متضمّنةً لكل أوامر الرسم التي طبقناها على الحاوية. وبناءً على ذلك، إذا أردنا أن نقرأ ألوان البكسلات، فسنضطّر لفعل شيءٍ ما لضمان اكتمال جميع عمليات الرسم، مثل أخذ لقطة شاشة للحاوية.
</p>

<p>
	يُمكِننا أن نأخذ لقطة شاشة لأي عقدة بمبيان المشهد، وتعيد تلك العملية قيمةً من النوع <code>WritableImage</code> تحتوي على صورة للعقدة بعد تطبيق جميع العمليات قيد الانتظار. تلتقط التعليمة التالية صورةً لعقدة بأكملها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_48" style="">
<span class="typ">WritableImage</span><span class="pln"> nodePic </span><span class="pun">=</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">snapshot</span><span class="pun">(</span><span class="pln">null</span><span class="pun">,</span><span class="pln">null</span><span class="pun">);</span></pre>

<p>
	يحتوي كائن الصنف <code>WritableImage</code> على صورةٍ للعقدة كما هي ظاهرةٌ على الشاشة، ويُمكِننا أن نَستخدِمه مثل أي كائن صورة عادي ينتمي إلى الصنف <code>Image</code>. يَستقبِل التابع من التعليمة السابقة معاملات من النوع <code>SnapshotParameter</code> و <code>WriteableImage</code>؛ فإذا مررنا صورةً غير فارغة قابلة للكتابة للمعامل الثاني، فسيَستخدِمها التابع مثل صورة طالما كانت كبيرةً كفاية لتَحمِل صورة العقدة، وربما يكون ذلك أكثر كفاءةً من إنشاء صورةٍ جديدة قابلة للكتابة. بالنسبة للمعامل الأول، يُمكِن اِستخدَامه للتحكُّم بالصورة الناتجة على نحوٍ أكبر، إذ يمكنه تحديدًا الطلب بأن تكون لقطة الشاشة مقتصرةً على مستطيلٍ معين من العقدة فقط.
</p>

<p>
	والآن، لكي نُنفِّذ أداة التلطيخ، ينبغي أن نقرأ البكسلات من مستطيلٍ صغيرٍ بالحاوية، إذ تبلغ مساحة ذلك المستطيل 9*9؛ ولإجراء ذلك بكفاءة، سنُمرِّر كائنًا من النوع <code>SnapshotParameter</code> لكي يُخصِّص رغبتنا بلقطة شاشة لذلك المستطيل لا الحاوية بالكامل، وبذلك نكون قد حصلنا على ذلك المستطيل ضمن كائن صورة من النوع <code>WritableImage</code>. وبعد ذلك، سنَستخدِم كائنًا من النوع <code>PixelReader</code> لقراءة ألوان البكسلات من الكائن الذي حصلنا عليه. في الواقع، يتضمَّن تنفيذ ذلك كثيرًا من التفاصيل، ولذلك سنعرض فقط طريقة التنفيذ.
</p>

<p>
	يُنشِئ البرنامج كائنًا واحدًا من كل نوعٍ من الأنواع التالية <code>WritableImage</code> و <code>PixelReader</code> و <code>SnapshotParameter</code> لاستخدامها مع جميع لقطات الشاشة، وقد تقع بعض البكسلات خارج الحاوية لبعض لقطات الشاشة، وهو ما يُعقدّ الأمور قليلًا. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_50" style="">
<span class="pln">pixels </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WritableImage</span><span class="pun">(</span><span class="lit">9</span><span class="pun">,</span><span class="lit">9</span><span class="pun">);</span><span class="pln">        </span><span class="com">// a 9-by-9 writable image</span><span class="pln">
pixelReader </span><span class="pun">=</span><span class="pln"> pixels</span><span class="pun">.</span><span class="pln">getPixelReader</span><span class="pun">();</span><span class="pln">  </span><span class="com">// a PixelReader for the writable image</span><span class="pln">
snapshotParams </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SnapshotParameters</span><span class="pun">();</span></pre>

<p>
	عندما ينقر المُستخدِم على زر الفأرة، ينبغي أن تُؤخذ لقطة شاشة مربعة لجزء الحاوية المحيط بمكان مؤشر الفأرة الحالي (startX,startY)، ثم تُنسخ بيانات الألوان من تلك اللقطة إلى مصفوفات مكونات الألوان <code>smudgeRed</code> و <code>smudgeGreen</code> و <code>smudgeBlue</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_52" style="">
<span class="pln">snapshotParams</span><span class="pun">.</span><span class="pln">setViewport</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Rectangle2D</span><span class="pun">(</span><span class="pln">startX </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> startY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="com">// 1</span><span class="pln">
canvas</span><span class="pun">.</span><span class="pln">snapshot</span><span class="pun">(</span><span class="pln">snapshotParams</span><span class="pun">,</span><span class="pln"> pixels</span><span class="pun">);</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> h </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pun">)</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">();</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> w </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pun">)</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> j </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> j </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">9</span><span class="pun">;</span><span class="pln"> j</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// صفٌ في لقطة الشاشة</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> startY </span><span class="pun">+</span><span class="pln"> j </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">;</span><span class="pln">  </span><span class="com">// الصف المقابل بالحاوية</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">9</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// عمودٌ في لقطة الشاشة</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> startX </span><span class="pun">+</span><span class="pln"> i </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">;</span><span class="pln">  </span><span class="com">// العمود المقابل بالحاوية</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">r </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> r </span><span class="pun">&gt;=</span><span class="pln"> h </span><span class="pun">||</span><span class="pln"> c </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> c </span><span class="pun">&gt;=</span><span class="pln"> w</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="com">// 2</span><span class="pln">
            smudgeRed</span><span class="pun">[</span><span class="pln">j</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Color</span><span class="pln"> color </span><span class="pun">=</span><span class="pln"> pixelReader</span><span class="pun">.</span><span class="pln">getColor</span><span class="pun">(</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> j</span><span class="pun">);</span><span class="pln">
                </span><span class="com">// pixelReader gets color from the snapshot</span><span class="pln">
            smudgeRed</span><span class="pun">[</span><span class="pln">j</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">();</span><span class="pln">
            smudgeGreen</span><span class="pun">[</span><span class="pln">j</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">();</span><span class="pln">
            smudgeBlue</span><span class="pun">[</span><span class="pln">j</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1] يُمثِّل <code>viewport</code> المستطيل الموجود بالحاوية والذي سيُضمَّن بلقطة الشاشة.
	</li>
	<li>
		[2] تعني أن النقطة (c,r) تقع خارج الحاوية، كما تشير قيمة <code>-1</code> بالمصفوفة <code>smudgeRed</code> إلى أن البكسل كان خارج الحاوية.
	</li>
</ul>
<p>
	والآن، علينا مزج اللون الموجود بمصفوفات مكونات الألوان بمربع البكسلات المحيط بالنقطة (x,y)؛ ولكي نُنفِّذ ذلك، سنأخذ لقطة شاشة مربعة جديدة لمربع البكسلات المحيط بالنقطة (x,y)؛ وبمجرد حصولنا على تلك اللقطة، يُمكِننا إجراء الحسابات الضرورية لعملية المزج، ونكتب بعدها اللون الجديد الناتج إلى الحاوية باستخدام كائن الصنف <code>PixelWriter</code> المسؤول عن الكتابة بالحاوية. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_55" style="">
<span class="pln">snapshotParams</span><span class="pun">.</span><span class="pln">setViewport</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Rectangle2D</span><span class="pun">(</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
canvas</span><span class="pun">.</span><span class="pln">snapshot</span><span class="pun">(</span><span class="pln">snapshotParams</span><span class="pun">,</span><span class="pln"> pixels</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> j </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> j </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">9</span><span class="pun">;</span><span class="pln"> j</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// صف بلقطة الشاشة</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> x </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> j</span><span class="pun">;</span><span class="pln">  </span><span class="com">// الصف المقابل بالحاوية</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">9</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// عمود بلقطة الشاشة</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> y </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">  </span><span class="com">// العمود المقابل بالحاوية</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> r </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> r </span><span class="pun">&lt;</span><span class="pln"> h </span><span class="pun">&amp;&amp;</span><span class="pln"> c </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> c </span><span class="pun">&lt;</span><span class="pln"> w </span><span class="pun">&amp;&amp;</span><span class="pln"> smudgeRed</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

         </span><span class="com">// اِسترجِع لون البكسل الحالي من لقطة الشاشة</span><span class="pln">
           </span><span class="typ">Color</span><span class="pln"> oldColor </span><span class="pun">=</span><span class="pln"> pixelReader</span><span class="pun">.</span><span class="pln">getColor</span><span class="pun">(</span><span class="pln">j</span><span class="pun">,</span><span class="pln">i</span><span class="pun">);</span><span class="pln"> 

              </span><span class="com">// 1</span><span class="pln">
           </span><span class="kwd">double</span><span class="pln"> newRed </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">oldColor</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()*</span><span class="lit">0.8</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeRed</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.2</span><span class="pun">);</span><span class="pln">
           </span><span class="kwd">double</span><span class="pln"> newGreen </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">oldColor</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()*</span><span class="lit">0.8</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeGreen</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.2</span><span class="pun">);</span><span class="pln">
           </span><span class="kwd">double</span><span class="pln"> newBlue </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">oldColor</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()*</span><span class="lit">0.8</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeBlue</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.2</span><span class="pun">);</span><span class="pln">

         </span><span class="com">// اكتب لون البكسل الجديد إلى الحاوية</span><span class="pln">
           pixelWriter</span><span class="pun">.</span><span class="pln">setColor</span><span class="pun">(</span><span class="pln"> c</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">newRed</span><span class="pun">,</span><span class="pln">newGreen</span><span class="pun">,</span><span class="pln">newBlue</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

              </span><span class="com">// امزج جزء من اللون الموجود بالحاوية إلى المصفوفات</span><span class="pln">
           smudgeRed</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> oldColor</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()*</span><span class="lit">0.2</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeRed</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.8</span><span class="pun">;</span><span class="pln">
           smudgeGreen</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> oldColor</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()*</span><span class="lit">0.2</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeGreen</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.8</span><span class="pun">;</span><span class="pln">
           smudgeBlue</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> oldColor</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()*</span><span class="lit">0.2</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> smudgeBlue</span><span class="pun">[</span><span class="pln">i</span><span class="pun">][</span><span class="pln">j</span><span class="pun">]*</span><span class="lit">0.8</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذ تعني [1]: احصل على لون جديد للبكسل عن طريق دمج اللون الحالي مع مكونات اللون المُخزَّنة بالمصفوفات.
</p>

<p>
	في الواقع، هذه عمليةٌ معقدةٌ نوعًا ما، ولكنها أوضحت طريقة التعامل مع البكسلات إلى حدٍ ما. عليك أن تتذكر أنه من الممكن كتابة ألوان البكسلات إلى الحاوية باستخدام كائنٍ ينتمي إلى الصنف <code>PixelWriter</code>؛ ولكن ينبغي لقراءة البكسلات منها، أن نأخذ لقطة شاشة للحاوية، ثم نَستخدِم كائنًا من الصنف <code>PixelReader</code> لقراءة ألوان البكسلات من كائن النوع <code>WritableImage</code> الذي يحتوي على لقطة الشاشة.
</p>

<h2>
	عمليات الدخل والخرج للصور
</h2>

<p>
	يحتوي البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter13/ToolPaint.java" rel="external nofollow">ToolPaint.java</a> على قائمة "File" تحتوي على أمرٍ لتحميل صورةٍ من ملفٍ إلى حاوية؛ وأمرٍ آخر لحفظ صورة من حاوية إلى ملف.
</p>

<p>
	بالنسبة لأمر تحميل الصورة، سنحتاج أولًا إلى تحميل الصورة إلى كائنٍ من النوع <code>Image</code>. كنا قد اطلعنا بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B9%D8%B6-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r1146/" rel="">التعرف على بعض أصناف مكتبة جافا إف إكس JavaFX البسيطة</a> على طريقة تحميل صورةٍ من ملف مورد، وكيفية رسمها ضمن حاوية. وبنفس الطريقة تقريبًا، يُمكِن تحميل صورة من ملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_57" style="">
<span class="typ">Image</span><span class="pln"> imageFromFile </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">(</span><span class="pln"> fileURL </span><span class="pun">);</span></pre>

<p>
	يُمثِّل المعامل سلسلةً نصيةً تُخصِّص موقع الملف بهيئة <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%88-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8%D8%9F-r1435/" rel="">محدّد موارد موّحد URL</a>، وهو ببساطة مسار الملف مسبوقٌ بكلمة "file:‎"؛ فإذا كان <code>imageFile</code> كائنًأ من النوع <code>File</code> يحتوي على مسار الملف، فيُمكِننا ببساطة كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_62" style="">
<span class="typ">Image</span><span class="pln"> imageFromFile </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">(</span><span class="pln"> </span><span class="str">"file:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> imageFile </span><span class="pun">);</span></pre>

<p>
	نَستخدِم عادةً كائن نافذة اختيار ملف من النوع <code>FileChooser</code> لنسمَح للمُستخدِم باختيار ملف -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/" rel="">مدخل إلى التعامل مع الملفات في جافا</a>-، وعندها سيكون <code>imageFile</code> هو الملف المختار الذي تُعيده تلك النافذة.
</p>

<p>
	قد يختار المُستخدِم ملفًا لا يُمكِن قراءته، أو لا يحتوي على صورة، وهنا لا يُبلِّغ باني كائن الصنف <code>Image</code> عن استثناءٍ في تلك الحالة، وإنما يَضبُط متغيرًا مُعرَّفًا بالكائن لكي يُشير إلى وجود خطأ؛ لذلك علينا فحص ذلك المتغير باستخدام الدالة <code>imageFromFile.isError()‎</code> التي تعيد قيمةً من النوع <code>boolean</code>، وفي حالة وجود خطأ فعلًا، يُمكِننا أن نَستعيد الاستثناء المُتسبِّب بالخطأ باستدعاء الدالة <code>imageFromFile.getException()‎</code>.
</p>

<p>
	بمجرد حصولنا على الصورة وتأكُّدنا من عدم وجود خطأ، يُمكِننا أن نرسمها بالحاوية باستخدام كائن السياق الرسومي الخاص بالحاوية. يَضبُط الأمر التالي حجم الصورة، بحيث تملأ الحاوية كاملةً:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_65" style="">
<span class="pln">g</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln"> imageFromFile</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">(),</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	سنضع جميع ما سبق ضمن تابعٍ، اسمه <code>doOpenImage()‎</code>، وسيَستدعيه البرنامج ToolPaint لتحميل الصورة المُخزَّنة بالملف الذي اختاره المُستخدِم. ألقِ نظرةً على تعريف التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_67" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doOpenImage</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">FileChooser</span><span class="pln"> fileDialog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileChooser</span><span class="pun">();</span><span class="pln"> 
    fileDialog</span><span class="pun">.</span><span class="pln">setInitialFileName</span><span class="pun">(</span><span class="str">""</span><span class="pun">);</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="pln">
                           </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">getProperty</span><span class="pun">(</span><span class="str">"user.home"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"Select Image File to Load"</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">File</span><span class="pln"> selectedFile </span><span class="pun">=</span><span class="pln"> fileDialog</span><span class="pun">.</span><span class="pln">showOpenDialog</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> selectedFile </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// لم يختر المُستخدِم أي ملف</span><span class="pln">
    </span><span class="typ">Image</span><span class="pln"> image </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">(</span><span class="str">"file:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> selectedFile</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">isError</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"Sorry, an error occurred while\ntrying to load the file:\n"</span><span class="pln">
                     </span><span class="pun">+</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">getException</span><span class="pun">().</span><span class="pln">getMessage</span><span class="pun">());</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    canvasGraphics</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="lit">0</span><span class="pun">,</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">getWidth</span><span class="pun">(),</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">getHeight</span><span class="pun">());</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	والآن، لكي نستخرِج الصورة من حاويةٍ إلى ملف، ينبغي أن نحصل أولًا على الصورة من الحاوية، وذلك بأخذ لقطة شاشة للحاوية بالكامل، وهو ما سنفعله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_69" style="">
<span class="typ">Image</span><span class="pln"> image </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">snapshot</span><span class="pun">(</span><span class="pln">null</span><span class="pun">,</span><span class="pln">null</span><span class="pun">);</span></pre>

<p>
	لسوء الحظ، لا تتضمَّن مكتبة JavaFX -بإصدارها الحالي على الأقل- خاصية حفظ الصور إلى ملفات؛ ولهذا سنعتمد على الصنف <code>BufferedImage</code> من حزمة <code>java.awt.image</code> بأداة تطوير واجهات المُستخدِمة الرسومية AWT القديمة، إذ يُمثِّل ذلك الصنف صورةً مُخزّنةً بذاكرة الحاسوب، مثل الصنف <code>Image</code> بمكتبة JavaFX. في الواقع، يُمكِننا بسهولة تحويل كائن من النوع <code>Image</code> إلى النوع <code>BufferedImage</code> باستخدام تابعٍ ساكن static مُعرَّف بالصنف <code>SwingFXUtils</code> من حزمة <code>javafx.embed.swing</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_72" style="">
<span class="typ">BufferedImage</span><span class="pln"> bufferedImage </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SwingFXUtils</span><span class="pun">.</span><span class="pln">fromFXImage</span><span class="pun">(</span><span class="pln">canvasImage</span><span class="pun">,</span><span class="pln">null</span><span class="pun">);</span></pre>

<p>
	يُمكِننا أن نُمرِّر كائنًا من النوع <code>BufferedImage</code> مثل معاملٍ ثانٍ للتابع ليَحمِل الصورة، والذي من الممكن أن يأخذ القيمة <code>null</code>.
</p>

<p>
	بمجرد حصولنا على كائن الصنف <code>BufferedImage</code>، يُمكِننا استخدام التابع الساكن التالي المُعرَّف بالصنف <code>ImageIO</code> من حزمة <code>javax.imageio</code> لكتابة الصورة إلى ملف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_74" style="">
<span class="typ">ImageIO</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln"> bufferedImage</span><span class="pun">,</span><span class="pln"> format</span><span class="pun">,</span><span class="pln"> file </span><span class="pun">);</span></pre>

<p>
	يُمثِّل المعامل الثاني للتابع السابق سلسلةً نصيةً من النوع <code>String</code>، إذ تخصِّص تلك السلسلة صيغة الملف الذي ستُحفَظ إليه الصورة. يُمكِن حفظ الصور عمومًا بعدة صيغ بما في ذلك "PNG" و "JPEG" و "GIF"، ويستخدم البرنامج ToolPaint صيغة "PNG" دائمًا.
</p>

<p>
	يُمثِل المعامل الثالث كائنًا من النوع <code>File</code>، ويُخصِّص بيانات الملف المطلوب حفظه، إذ يُبلِّغ التابع <code>ImageIO.write()‎</code> عن استثناءٍ، إذا لم يتمكَّن من حفظ الملف؛ وإذا لم يتعرف التابع على الصيغة، فإنه يَفشَل أيضًا، ولكنه لا يُبلِّغ عن استثناء.
</p>

<p>
	يُمكِننا الآن أن نُضمِّن كل شيء معًا داخل التابع <code>doSaveImage()‎</code> بالبرنامج ToolPaint. ألقِ نظرةً على تعريف التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9316_76" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doSaveImage</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">FileChooser</span><span class="pln"> fileDialog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileChooser</span><span class="pun">();</span><span class="pln"> 
    fileDialog</span><span class="pun">.</span><span class="pln">setInitialFileName</span><span class="pun">(</span><span class="str">"imagefile.png"</span><span class="pun">);</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="pln">
                     </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">getProperty</span><span class="pun">(</span><span class="str">"user.home"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"Select File to Save. Name MUST end with .png!"</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">File</span><span class="pln"> selectedFile </span><span class="pun">=</span><span class="pln"> fileDialog</span><span class="pun">.</span><span class="pln">showSaveDialog</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> selectedFile </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// لم يختر المُستخدِم أي ملف</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Image</span><span class="pln"> canvasImage </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">snapshot</span><span class="pun">(</span><span class="pln">null</span><span class="pun">,</span><span class="pln">null</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">BufferedImage</span><span class="pln"> image </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SwingFXUtils</span><span class="pun">.</span><span class="pln">fromFXImage</span><span class="pun">(</span><span class="pln">canvasImage</span><span class="pun">,</span><span class="pln">null</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">String</span><span class="pln"> filename </span><span class="pun">=</span><span class="pln"> selectedFile</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">().</span><span class="pln">toLowerCase</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> filename</span><span class="pun">.</span><span class="pln">endsWith</span><span class="pun">(</span><span class="str">".png"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"The file name must end with \".png\"."</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        boolean hasFormat </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageIO</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="str">"PNG"</span><span class="pun">,</span><span class="pln">selectedFile</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> hasFormat </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// لا ينبغي أن يحدث ذلك نهائيًا</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="pln"> </span><span class="str">"PNG format not available."</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
               </span><span class="str">"Sorry, an error occurred while\ntrying to save the image:\n"</span><span class="pln">
                     </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">getMessage</span><span class="pun">());</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">    
</span><span class="pun">}</span></pre>

<p>
	ترجمة -بتصرّف- للقسم <a href="https://math.hws.edu/javanotes/c13/s2.html" rel="external nofollow">Section 2: Fancier Graphics</a> من فصل Chapter 13: GUI Programming Continued من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1525/" rel="">الخاصيات والارتباطات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">مقدمة إلى برمجة واجهات المستخدم الرسومية (GUI) في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1526</guid><pubDate>Tue, 05 Apr 2022 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62E;&#x627;&#x635;&#x64A;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x627;&#x631;&#x62A;&#x628;&#x627;&#x637;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1525/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/6250032c9d587_--Bindings.png.ae2d4a31be0d44fe045c9e0ac0c82c6d.png" /></p>

<p>
	تعتمد <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">برمجة واجهات المُستخدِم الرسومية GUI</a> على اِستخدَام الأحداث events استخدامًا كبيرًا، متضمنةً الأحداث منخفضة المستوى، مثل أحداث لوحة المفاتيح والفأرة، والأحداث عالية المستوى، مثل تلك الناتجة عن اختيار قائمة أو ضبط قيمة مزلاج. تَنتُج أحداث المزلاج -كما رأينا في في مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%AE%D8%B7%D9%8A%D8%B7-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1149/" rel="">التخطيط الأساسي لواجهة المستخدم في مكتبة جافا إف إكس JavaFX</a> - من خاصية قابلة للمراقبة observable مُعرَّفة بذلك المزلاج، وبالتالي إذا أردنا الإستجابة إلى تغيُّر قيمة مزلاج <code>sldr</code>، فعلينا أن نُسجِّل مُستمِعًا listener بالخاصية <code>valueProperty</code> المُعرَّفة به على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_8" style="">
<span class="pln">sldr</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	عندما تتغير قيمة المزلاج، تُولِّد الخاصية <code>valueProperty</code> حدثًا، مما يَسمَح لشيفرة معالجة الأحداث بالاستجابة إلى ذلك التغيير.
</p>

<p>
	في الواقع، قيمة <code>valueProperty</code> للمزلاج هي <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-objects-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-methods-%D9%88%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-variables-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1108/" rel="">كائنٌ object</a> من النوع <code>DoubleProperty</code>، إذ تُغلِّف كائنات هذا النوع قيمةً من النوع <code>double</code>، وتحتوي على التابعين <code>get()‎</code> و <code>set()‎</code> لاسترجاع تلك القيمة وتعديلها. علاوةً على ذلك، تُعدّ تلك القيمة قيمةً قابلةً للمراقبة، مما يَعنِي أنها تُولِّد أحداثًا عندما تتغيَّر، كما أنها تُعَد أيضًا خاصيةً قابلةً للارتباط bindable؛ إذ يستطيع هذا النوع من الخاصيات أن يرتبط بخاصياتٍ أخرى من نفس النوع، وتُجبَر الخاصيات المُرتبطِة بتلك الطريقة على أن يكون لها نفس القيمة.
</p>

<p>
	تَستخدِم واجهة تطوير تطبيقات <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150/" rel="">مكتبة JavaFX</a> الخاصيات القابلة للارتباط بكثرة، وسنتعلّم بهذا المقال كيفية استخدامها والدور الذي تلعبه.
</p>

<p>
	الأصناف التي سنناقشها بهذا المقال مُعرَّفةٌ بحزمة <code>javafx.beans</code> وحزمها الفرعية، ومع ذلك لن نضطّر لاستيراد import تلك الأصناف من الحزم الموجودة بها إلى برامج <a href="https://academy.hsoub.com/programming/java/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A8%D8%B3%D9%8A%D8%B7-r1145/" rel="">JavaFX</a>؛ لأننا غالبًا ما نعتمد على كائناتٍ موجودة بالفعل.
</p>

<h2>
	القيم القابلة للمراقبة Observable
</h2>

<p>
	تُعدّ الكثير من متغيرات النسخ instance variables المُعرَّفة بكائنات مكتبة JavaFX، قيمًا قابلةً للمراقبة، أي أنها تُولِّد أحداثًا، حتى أن أغلبها قابلٌ للارتباط أيضًا؛ إذ يُعدّ كلٌ من عرض حاوية canvas وارتفاعها مثلًا، قيمًا قابلةً للمراقبة من النوع <code>DoubleProperty</code>؛ كما يُعدّ النص الموجود بحقل نصي أو عنوان label قيمةً قابلةً للمراقبة من النوع <code>StringProperty</code>؛ إلى جانب أن قائمة أبناء حاوية من النوع <code>Pane</code> تُعَد قيمةً قابلةً للمراقبة من النوع <code>ObservableList&lt;Node&gt;‎</code>. ويتكَّون مربع الاختيار checkbox من خاصية قابلة للمراقبة من النوع <code>BooleanProperty</code>، والتي تشير إلى اختيار المربع؛ وفي مثال آخر، نجد لون النص المعروض بعنوان، فهو أيضًا قيمةٌ قابلة للمراقبة من النوع <code>ObjectProperty&lt;Paint&gt;‎</code>.
</p>

<p>
	تُولِّد القيم القابلة للمراقبة نوعين من الأحداث هما: الحدث الأول، وهو حدث تغيُّر القيمة، وفي تلك الحالة لا بُدّ أن يُنفِّذ implement معالج الحدث واجهة نوع الدالة <code>ChangeListener&lt;T&gt;‎</code> ذات المعاملات غير مُحدّدة النوع parameterized، والتي تحتوي على التابع <code>changed(target,oldValue,newValue)‎</code>؛ إذ يُمثِّل المعامل الأول الكائن القابل للمراقبة الذي تغيَّرت قيمته، ويحتوي المعامل الثاني على القيمة السابقة؛ في حين يحتوي المعامل الثالث على القيمة الجديدة. على سبيل المثال، إذا أدرنا عرض القيمة الحالية لمزلاج مثل نص عنوان، فعندها ينبغي أن نُغيّر النص عندما تتغير قيمة المزلاج، بحيث تتوافق قيمتهما دائمًا. ولهذا، علينا أن نُسجِّل مُستمعًا لحدث تغيُّر القيمة لكي يَضبُط نص العنوان ليتوافق مع قيمة المزلاج، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_13" style="">
<span class="pln">slider</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> 
            </span><span class="pun">(</span><span class="pln">t</span><span class="pun">,</span><span class="pln">oldVal</span><span class="pun">,</span><span class="pln">newVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> label</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Slider Value: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> newVal</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	سنرى لاحقًا أن هناك طرائقٌ أفضل لإنجاز نفس المهمة.
</p>

<p>
	تُولِّد القيم القابلة للمراقبة نوعًا آخرًا من الأحداث، هو حدث انعدام الصلاحية invalidation؛ إذ يُولَّد ذلك الحدث عندما تُصبِح القيمة الحالية غير صالحةٍ لسببٍ ما. لا يستطيع أي كائنٍ عمومًا قراءة القيمة غير الصالحة لكائنٍ آخر، فعند محاولته قراءتها، سيُعاد حسابها لتُصبِح قيمةً صالحةً مرةً أخرى. بمعنًى آخر، يُشيِر حدث انعدام الصلاحية إلى ضرورة إعادة حساب القيمة، ولكنها لا تُحسَب فعليًا إلا عند الحاجة؛ ويُطلَق على ذلك اسم "التحصيل المُرجَأ lazy evaluation" للقيمة، أي أن عملية إعادة حساب القيمة تُؤجَّل إلى حين الحاجة إلى استخدام القيمة الجديدة لسببٍ ما.
</p>

<p>
	يتميز هذا النوع من التحصيل بالكفاءة بالموازنة مع إعادة حساب القيمة بمجرد انعدام صلاحيتها. لنفترض مثلًا، أن عشرات الأشياء قد حدثت، وأدت لجعل قيمةٍ ما غير صالحة مع استخدام التحصيل المُرجَأ، وبالتالي سيُعاد حساب القيمة مرةً واحدةً فقط عند الحاجة إليها بدلًا من أن يُعاد تحصيلها عشرات المرات. والأهم من ذلك هو أن الكائن القابل للمراقبة يُولِّد حدث انعدام صلاحية وحيد لا عشرة أحداث، ويُجنبِّنا ذلك العديد من الاستدعاءات غير الضرورية لمستمعي ذلك الحدث.
</p>

<p>
	بالنسبة لمكتبة JavaFX، يُفضَّل عادةً تهيئة مُستمِع إلى حدث انعدام الصلاحية بدلًا من حدث تغيُّر القيمة.
</p>

<p>
	يُعرِّف مستمعو الأحداث من النوع <code>InvalidationListener</code> تابعًا وحيدًا، هو التابع <code>invalidated(obv)‎</code>، إذ يُشير معامل ذلك التابع إلى الكائن القابل للمراقبة الذي أصبحت قيمته غير صالحة. ألقِ نظرةً على الشيفرة التالية على سبيل المثال، مع الملاحظة أن <code>sayHello</code> هو مربع اختيار:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_15" style="">
<span class="pln">sayHello</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sayHello</span><span class="pun">.</span><span class="pln">isSelected</span><span class="pun">())</span><span class="pln">
        label</span><span class="pun">.</span><span class="pln">settext</span><span class="pun">(</span><span class="str">"Hello"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        label</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Goodbye"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تُسجِّل الشيفرة بالمثال السابق <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r690/" rel="">مستمع حدث</a> من النوع <code>InvalidationListener</code>. ويُمكِننا تنفيذ نفس الأمر باستدعاء <code>sayHello.setOnAction()‎</code>، ولكن هنالك اختلاف: يُستدعَى مُستمعِو الأحداث من النوع <code>ActionListener</code>، فقط إذا كانت حالة مربع الاختيار قد تغيَّرت بواسطة المُستخدِم؛ بينما يُستدعَى مُستمعِي أحداث انعدام الصلاحية متى تغيّرت القيمة، بما في ذلك التغييرات المُجراة نتيجة لاستدعاء <code>sayHello.setSelected()‎</code>.
</p>

<p>
	لاحِظ أننا كنا قد اِستخدَمنا نفس التابع <code>addListener()‎</code> لتسجيل مستمعي الأحداث من النوع <code>ChangeListener</code>، إذ يَستطيع المُصرِّف compiler أن يُفرِّق بينهما حتى لو مرَّرنا المُستمِع بهيئة <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%A8%D9%8A%D8%B1%D8%A7%D8%AA-%D9%84%D8%A7%D9%85%D8%AF%D8%A7-lambda-expressions-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1090/" rel="">تعبير لامدا lambda expression</a>؛ لأن تعبير لامدا لحدث من النوع <code>ChangeListener</code> يَستقبِل ثلاثة معاملات؛ في حين أن تعبير لامدا لحدث من النوع <code>InvalidationListener</code>، يَستقبِل معاملًا واحدًا.
</p>

<p>
	يَطرَح ذلك السؤال التالي: ماذا سيحدث لو كانت قيمة الخاصية <code>selectedProperty</code> بمربع الاختيار <code>sayHello</code> قد أصبحت غير صالحةٍ فقط دون أن تتغير؟ هل سيعيد الاستدعاء <code>sayHello.isSelected()‎</code> القيمة الحالية غير الصالحة أم القيمة الجديدة؟ في الحقيقة، سيؤدي استدعاء <code>sayHello.isSelected()‎</code> إلى تحصيل القيمة الجديدة وإعادتها. لا يُمكِن عمومًا قراءة القيم غير الصالحة نهائيًا؛ إذ تؤدي أي محاولة لقراءتها إلى إعادة تحصيلها ما يَنتُج عنه حساب القيمة الجديدة.
</p>

<p>
	بالنسبة لمكتبة JavaFX، يُمكِن الوصول إلى الخاصيات القابلة للمراقبة ضمن كائنٍ ما باستدعاء توابع نسخ تنتهي أسماؤها بكلمة "Property"، إذ يُستخدَم الاستدعاء <code>slider.valueProperty()‎</code> مثلًا، للوصول إلى خاصية القيمة بمزلاج؛ بينما يُستخدَم الاستدعاء <code>label.textProperty()‎</code> للوصول إلى خاصية النص بعنوان. ليست جميع القيم القابلة للقراءة بكائنات مكتبة JavaFx خاصيات؛ في حين تُعدّ الخاصيات القابلة للمراقبة قابلةً للارتباط أيضًا -كما سنرى بالمقال التالي-؛ كما لا تُعدّ القيم البسيطة القابلة للمراقبة قابلة للارتباط، مع أنها ما تزال تُولِّد حدثي تغيُّر القيمة وانعدام الصلاحية، بل ويُمكِن تسجيل مستمعي أحداث للاستجابة إلى التغيُّرات التي تحدث بقيمتها.
</p>

<h2>
	الخاصيات القابلة للارتباط Bindable
</h2>

<p>
	يمكن لمعظم الخاصيات القابلة للمراقبة بمكتبة JavaFX الارتباط بخاصيةٍ أخرى من نفس النوع، وتًستثنى من ذلك الخاصيات القابلة للقراءة فقط read-only؛ فلا يُمكِن ربطها بخاصية أخرى، على الرغم من إمكانية ربط خاصيةٍ بخاصية قابلة للقراءة فقط. لنفترض مثالًا بسيطًا بأننا نريد لنص عنوان معين أن يكون دائمًا هو نفس قيمة النص الموجود بحقلٍ نصي معين. يُمكِننا تنفيذ ذلك ببساطة بربط خاصية <code>textProperty</code> المُعرَّفة بالعنوان مع خاصية <code>textProperty</code> المُعرَّفة بالحقل النصي، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_17" style="">
<span class="typ">Label</span><span class="pln"> message </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">(</span><span class="str">"Never Seen"</span><span class="pun">);</span><span class="pln">
</span><span class="typ">TextField</span><span class="pln"> input </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TextField</span><span class="pun">(</span><span class="str">"Type Here!"</span><span class="pun">);</span><span class="pln">
message</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> input</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	يُجبِر التابع <code>bind()‎</code> قيمة <code>message.textProperty()‎</code> بأن تكون هي نفسها قيمة <code>input.textProperty()‎</code>. وبمجرد تنفيذ التابع <code>bind()‎</code>، يُنسَخ النص الموجود بالحقل النصي إلى العنوان؛ لكي لا يرى المُستخدِم نص العنوان المبدئي (وهو "Never Seen" بالمثال السابق). عند حدوث أي تغيير على النص الموجود بالحقل النصي أثناء تشغيل البرنامج، يُطبَّق ذلك التغيير على العنوان تلقائيًا، سواءً كان ذلك التغيير نتيجةً لكتابة المُستخدِم بالحقل النصي، أو نتيجةً لاستدعائنا للتابع <code>input.setText()‎</code> برمجيًا. لاحِظ أن مفهوم الارتباط مُنفَّذ داخليًا باستخدام الأحداث ومستمعي الأحداث، فالهدف منه هو ألا نضطّر لتهيئة مستمعي الأحداث يدويًا، وإنما نَستدعِي فقط التابع <code>bind()‎</code> لتهيئة كل شيء أوتوماتيكيًا.
</p>

<p>
	عندما نَستخدِم <code>bind()‎</code> لربط خاصية بخاصيةٍ أخرى، لا يُمكِننا عندها تعديل قيمة الخاصية المُرتَبطِة تعديلًا مباشرًا، ولذلك يؤدي أي استدعاء للتابع <code>message.setText()‎</code> بالمثال السابق إلى حدوث <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%AA-exceptions-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1068/" rel="">استثناء exception</a>. وبالطبع، بإمكان أي خاصيةٍ الارتباط بخاصيةٍ واحدةٍ أخرى فقط بأي لحظة. يُمكِننا استخدام التابع <code>unbind()‎</code> الذي لا يَستقبِل أي معاملات، وذلك لإلغاء ارتباطٍ معين على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_20" style="">
<span class="pln">message</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">unbind</span><span class="pun">();</span></pre>

<p>
	يحتوي البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter13/BoundPropertyDemo.java" rel="external nofollow">BoundPropertyDemo.java</a> على أمثلةٍ متعددة عن الخاصيات المرتبطة. ستَجِد كما في المثال السابق، بأن خاصية النص الموجودة بعنوان مرتبطةً بخاصية النص الموجودة بحقلٍ نصي، بحيث تؤدي الكتابة في الحقل النصي إلى تعديل نص العنوان. تعرِض الصورة التالية لقطة شاشة من البرنامج:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="95835" href="https://academy.hsoub.com/uploads/monthly_2022_04/001Bound_Property_Demo.png.f63ddc15693416d32b595fbb43384c48.png" rel=""><img alt="001Bound_Property_Demo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="95835" data-unique="oincrvnzw" src="https://academy.hsoub.com/uploads/monthly_2022_04/001Bound_Property_Demo.png.f63ddc15693416d32b595fbb43384c48.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	يُمثِّل العنوان الموجود على يمين أسفل النافذة مثالًا آخرًا عن الارتباط، إذ يَعرِض العنوان قيمة المزلاج، وسيتغير نص العنوان بمجرد أن يَضبُط المُستخدِم قيمة المزلاج. كما ناقشنا بالأعلى، يُمكِننا تنفيذ ذلك بتسجيل مستمعٍ إلى خاصية المزلاج <code>valueProperty</code>، ولكنها مُنفَّذةٌ بهذا المثال باستخدام الارتباط. ومع ذلك توجد مشكلة، إذ أن خاصية <code>textProprety</code> الموجودة بالعنوان من النوع <code>StringProperty</code>، بينما خاصية <code>valueProperty</code> الموجودة بالمزلاج من النوع <code>DoubleProperty</code>، ولذلك لا يُمكِن ربطهما مباشرةً؛ لأن الإرتباط يَعمَل بنجاح فقط إذا كانت كلتاهما من نفس النوع. لحسن الحظ، يحتوي النوع <code>DoubleProperty</code> على التابع <code>asString()‎</code> الذي يُحوِّل الخاصية إلى النوع <code>string</code>، أي لو كان <code>slider</code> مزلاجٌ من النوع <code>Slider</code>، فسيُمثِّل مايلي خاصيةً من النوع <code>string</code> تحتوي على التمثيل النصي لقيمة المزلاج العددية التي هي بالأساس من النوع <code>double</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_24" style="">
<span class="pln">slider</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">asString</span><span class="pun">()</span></pre>

<p>
	وبالتالي يُمكِننا الآن أن نربط خاصية <code>textProprety</code> الموجودة بالعنوان بتلك الخاصية النصية، كما يمكن للتابع <code>asString()‎</code> أن يَستقبِل صيغة سلسلة تنسيق format string، مثل تلك التي يَستخدِمها التابع <code>System.out.printf</code>، لتنسيق قيمة المزلاج العددية. وقد اِستخدَمنا المتغير <code>sliderVal</code> لتمثيل العنوان بهذا البرنامج، وربطنا خاصيته النصية بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_26" style="">
<span class="pln">sliderVal</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> 
                   slider</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">asString</span><span class="pun">(</span><span class="str">"Slider Value: %1.2f"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذا شئنا الدقة، يُعدّ التعبير <code>slider.valueProperty().asString()‎</code> من النوع <code>StringBinding</code> وليس <code>StringProperty</code>، ولكننا سنتجاهل التمييز بينهما هنا لأنه ليس مهمًا.
</p>

<p>
	تحتوي الكائنات المُمثِّلة لخاصيات Property على توابع كثيرة للتحويل بين الأنواع بالإضافة إلى توابع لإجراء بعض العمليات الأخرى، إذ يُعرِّف النوع <code>DoubleProperty</code> التابع <code>lessThan(number)‎</code>، الذي يعيد خاصيةً من النوع <code>boolean</code> قيمتها تساوي <code>true</code> عندما تكون قيمة النوع<code>DoubleProperty</code> أصغر من عددٍ معين. فمثلًا، إذا كان <code>btn</code> من النوع <code>Button</code>، فإنه يحتوي على خاصية <code>btn.disableProperty()‎</code> من النوع <code>BooleanProperty</code> للإشارة إلى ما إذا كان الزر مُعطَّلًا أم لا؛ فإذا أردنا أن نُعطِّل الزر عندما تصبح قيمة مزلاج معين أقل من 20، يُمكِننا ربط خاصية تعطيل الزر على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_28" style="">
<span class="pln">btn</span><span class="pun">.</span><span class="pln">disableProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> slider</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">lessThan</span><span class="pun">(</span><span class="lit">20</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	تتوفَّر توابع أخرى مشابهة، مثل <code>greaterThan()‎</code> و <code>lessThanOrEqual()‎</code> و <code>isNotEqualTo()‎</code> وغيرها، كما تتوفَّر توابع أخرى لإجراء بعض العمليات الحسابية. على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_30" style="">
<span class="pln">slider</span><span class="pun">.</span><span class="pln">valueProperty</span><span class="pun">().</span><span class="pln">multiply</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span></pre>

<p>
	يُمثِّل التعبير السابق خاصيةً من النوع <code>double</code> قيمتها تساوي ضعف قيمة المزلاج.
</p>

<p>
	يُمكِننا استخدام الصنف <code>When</code> المُعرَّف بحزمة <code>javafx.beans.binding</code> لكي نُطبِّق شيئًا مثل المعامل الثلاثي ":?" على خاصيات من النوع <code>boolean</code>، ولكن بقواعد صيغة مختلفة بعض الشيء -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A8%D9%8A%D8%B1%D8%A7%D8%AA-expressions-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1019/" rel="">التعبيرات expressions في جافا</a>-. على سبيل المثال، إذا كان <code>boolProp</code> يُمثِّل خاصيةً من النوع <code>boolean</code>، وكان <code>trueVal</code> و <code>falseVal</code> متغيرين يحتويان على أي قيم بشرط أن تكون من نفس النوع، فإن:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_33" style="">
<span class="kwd">new</span><span class="pln"> </span><span class="typ">When</span><span class="pun">(</span><span class="pln">boolProp</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">trueVal</span><span class="pun">).</span><span class="pln">otherwise</span><span class="pun">(</span><span class="pln">falseVal</span><span class="pun">)</span></pre>

<p>
	يُمثِّل خاصيةً من نفس نوع المتغيرين <code>trueVal</code> و <code>falseVal</code>، وتكون قيمتها مساويةً لقيمة <code>trueVal</code> إذا كان <code>boolProp</code> يحتوي على القيمة <code>true</code>، أو القيمة <code>falseVal</code> إذا كان <code>boolProp</code> يحتوي على القيمة <code>false</code>.
</p>

<p>
	كنا قد اِستخدَمنا بمثال سابق مستمعًا لضبط نص عنوان إلى كلمة "Hello" أو "Goodbye"، وذلك بناءً على اختيار مربع الاختيار <code>sayHello</code> أم لا. تُوضِّح الشيفرة التالية طريقة فعل نفس الشيء، ولكن باستخدام ارتباط الخاصيات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_35" style="">
<span class="pln">label</span><span class="pun">.</span><span class="pln">textProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> 
     </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">When</span><span class="pun">(</span><span class="pln">sayHello</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">()).</span><span class="pln">then</span><span class="pun">(</span><span class="str">"Hello"</span><span class="pun">).</span><span class="pln">otherwise</span><span class="pun">(</span><span class="str">"Goodbye"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	يُمثِل المعامل المُمرَّر بالتعليمة <code>When(sayHello.selectedProperty())‎</code> خاصيةً من النوع <code>boolean</code>. نظرًا لأن كُلًا من "Hello" و "Goodbye" قيمٌ من النوع <code>String</code>، فستكون الخاصية الناتجة من التعبير بالكامل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">سلسلةً نصيةً من النوع String</a>، وهو ما يتوافق مع نوع الخاصية <code>label.textProperty()‎</code>.
</p>

<p>
	بنفس الأسلوب، يَستخدِم البرنامج <a href="http://math.hws.edu/javanotes/source/chapter13/BoundPropertyDemo.java" rel="external nofollow">BoundPropertyDemo.java</a> مربع اختيار للتحكُّم بلون الخلفية الخاص بعنوان كبير باستخدام الارتباط مع كائنٍ من النوع <code>When</code>، ويُمكِنك الاطلاع على الشيفرة كاملة لمزيدٍ من التفاصيل.
</p>

<p>
	يُمثِّل البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter13/CanvasResizeDemo.java" rel="external nofollow">CanvasResizeDemo.java</a> تطبيقًا مباشرًا ومفيدًا على ارتباط الخاصيات؛ إذ يَعرِض هذا البرنامج 50 قرصًا أحمرًا صغيرًا داخل نافذة، وتتحرك الأقراص ضمن حاويةٍ من النوع <code>Canvas</code> تملأ النافذة، وتتصادم مع بعضها بعضًا، كما تتصادم بحواف الحاوية.
</p>

<p>
	كنا قد ضبطنا حجم النافذة في الأمثلة السابقة التي اِستخدَمنا بها حاوية، ليكون غير قابل للتعديل؛ وذلك لأن حجم الحاوية لا يتغير أوتوماتيكيًا، ومع ذلك، بإمكان البرامج أن تُعدِّل حجم الحاوية من خلال ضبط الخاصيات الممثلة لعرض الحاوية وارتفاعها، وذلك باستدعاء <code>canvas.setWidth(w)‎</code> و <code>canvas.setHeight(h)‎</code>. هناك حلٌ آخر لضبط حجم الحاوية، إذ يُمكِننا ربط هاتين الخاصيتين بمصدرٍ مناسب؛ لأنهما خاصيتان قابلتان للارتباط من النوع <code>DoubleProperty</code>.
</p>

<p>
	استخدمنا بهذا البرنامج كائنًا من النوع <code>Pane</code> ليحيط بالحاوية، ويعمل مثل جذرٍ لمبيان المشهد scene graph، ويملأ النافذة. وعندما يُعدِّل المُستخدِم حجم النافذة، يتغير حجم الكائن تلقائيًا ليتوافق مع ما اختاره المُستخدِم. إذا أردنا لحجم الحاوية أن يتغير تلقائيًا هو الآخر ليتماشى مع حجم الكائن، فعلينا أن نربط خاصية عرض الحاوية بخاصية عرض الكائن؛ وبالمثل خاصية ارتفاع الحاوية بخاصية ارتفاع الكائن. تُوضِح الشيفرة التالية طريقة فعل ذلك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_39" style="">
<span class="pln">canvas</span><span class="pun">.</span><span class="pln">widthProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> root</span><span class="pun">.</span><span class="pln">widthProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> 
canvas</span><span class="pun">.</span><span class="pln">heightProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> root</span><span class="pun">.</span><span class="pln">heightProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	إذا شغَّلت البرنامج وزدت حجم النافذة، فسترى أن حجم الحاوية قد ازداد أيضًا؛ إذ ستتمكَّن الأقراص الحمراء المتصادمة من الانتشار بحيز أكبر؛ وبالمثل، إذا قلصت حجمها، ستتقيد الأقراص ضمن حيِّز أصغر.
</p>

<p>
	يعيد البرنامج رسم الحاوية باستمرار، ولهذا لسنا مضطّرين لفعل أي شيء إضافي لإعادة رسمها عند زيادة حجمها. قد نحتاج ببرامج أخرى إلى إعادة رسم محتويات الحاوية عند تغيُّر حجمها، وهو ما يُمكِننا تنفيذه بإضافة مستمعي أحداث إلى خاصيات عرض الحاوية وارتفاعها، لكي يتمكَّنوا من إعادة رسم الحاوية استجابةً للتغيُّرات الواقعة بعرضها أو ارتفاعها.
</p>

<h2>
	الارتباط ثنائي الاتجاه
</h2>

<p>
	يُستخدَم التابع <code>bind()‎</code> لإنشاء ارتباطاتٍ أحادية الاتجاه؛ بمعنى أنها تَعمَل من اتجاهٍ واحدٍ فقط، وهو ما قد لا يكون مناسبًا في بعض الأحيان. لنفترض مثلًا أنه لدينا مربعي اختيار <code>cb1</code> و <code>cb2</code> من النوع <code>CheckBox</code>، ونريدهما أن يكونا متطابقين دائمًا. في الواقع، لا يُمكِننا تحقيق ذلك باستخدام الارتباطات أحادية الاتجاه؛ لأننا لو كتبنا ما يلي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_41" style="">
<span class="pln">cb2</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">().</span><span class="pln">bind</span><span class="pun">(</span><span class="pln"> cb1</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	فستصبح حالة مربع الاختيار <code>cb2</code> مطابقةً لحالة مربع الاختيار <code>cb1</code>، ولكن لن يؤثر تغيير حالة <code>cb2</code> على حالة <code>cb1</code>، وإنما يؤدي إلى حدوث استثناء؛ لأنه لا يَصِح تعديل خاصيةٍ قد رُبِطَت فعليًا باستخدام <code>bind()‎</code>. يَعنِي ذلك، أنه لو نقر المُستخدِم على مربع الاختيار <code>cb2</code>، فسيحدث استثناء، وذلك لأن مربع الاختيار سيحاول أن يُغيّر حالته.
</p>

<p>
	يَكْمُن الحل في ما يُعرَف باسم <strong>الارتباط ثنائي الاتجاه bidirectional binding</strong>؛ فعند ارتباط خاصيتين ارتباطًا ثنائي الاتجاه، عندها يُمكِن لقيمة أيٍّ منهما أن تتغير، وعندها تتغيّر قيمة الأخرى أوتوماتيكيًا إلى نفس القيمة. يُستخدَم التابع <code>bindBidirectional()‎</code> لإنشاء هذا النوع من الارتباط، فيُمكِننا مثلًا كتابة ما يَلِي لربط مربعي الاختيار:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_44" style="">
<span class="pln">cb2</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">().</span><span class="pln">bindBidirectional</span><span class="pun">(</span><span class="pln"> cb1</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	والآن، يَستطيع المُستخدِم أن ينقر على أي مربعٍ منهما، وستتغير حالة المربع الآخر تلقائيًا. قد لا يكون ذلك مفيدًا لمربعي اختيار، ولكن أهميته تبرز في حالات أخرى، مثل مزامنة حالة مربع اختيار من النوع <code>CheckBox</code> معروض بالنافذة مع حالة عنصر مربع اختيار ضمن قائمةٍ من النوع <code>CheckMenuItem</code>، إذ سيتمكَّن المُستخدِم من اِستخدَام أحد عنصري الواجهة لإتمام نفس الأمر. في الواقع، يشيع استخدام مُكوِّنات واجهة لها نفس الغرض ضمن القوائم وأشرطة الأدوات باتباع نفس الاسلوب.
</p>

<p>
	يُطبِّق البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter13/BoundPropertyDemo.java" rel="external nofollow">BoundPropertyDemo.java</a> أمرًا مشابهًا مع أزرار انتقاء من النوع <code>RadioButton</code> وعناصر أزرار انتقاء ضمن قائمة من النوع <code>RadioMenuItem</code>، إذ يَسمَح البرنامج للمُستخدِم بالتحكُّم بلون عنوان معين، إما باستخدام قائمة "Color"، أو مجموعةٍ من أزرار الانتقاء؛ إذ يرتبط كل زر انتقاء ارتباطًا ثنائي الاتجاه مع عنصر زر انتقاء مكافئ معروض بالقائمة. تُوضِّح الشيفرة التالية طريقة تنفيذ ذلك بالتفصيل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2986_46" style="">
<span class="typ">Menu</span><span class="pln"> colorMenu </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Menu</span><span class="pun">(</span><span class="str">"Color"</span><span class="pun">);</span><span class="pln">

</span><span class="typ">Color</span><span class="pun">[]</span><span class="pln"> colors </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">BLACK</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">RED</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">GREEN</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">BLUE </span><span class="pun">};</span><span class="pln">
</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> colorNames </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">"Black"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Red"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Green"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Blue"</span><span class="pln"> </span><span class="pun">};</span><span class="pln">

</span><span class="typ">ToggleGroup</span><span class="pln"> colorGroup </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ToggleGroup</span><span class="pun">();</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> colors</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// أنشِئ عنصر قائمة وزر انتقاء مقابل</span><span class="pln">

    </span><span class="typ">RadioButton</span><span class="pln"> button </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RadioButton</span><span class="pun">(</span><span class="pln">colorNames</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span><span class="pln">
    </span><span class="typ">RadioMenuItem</span><span class="pln"> menuItem </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RadioMenuItem</span><span class="pun">(</span><span class="pln">colorNames</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span><span class="pln">

    button</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">().</span><span class="pln">bindBidirectional</span><span class="pun">(</span><span class="pln"> menuItem</span><span class="pun">.</span><span class="pln">selectedProperty</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

    menuItem</span><span class="pun">.</span><span class="pln">setToggleGroup</span><span class="pun">(</span><span class="pln">colorGroup</span><span class="pun">);</span><span class="pln">

        </span><span class="com">// 1 </span><span class="pln">
    menuItem</span><span class="pun">.</span><span class="pln">setUserData</span><span class="pun">(</span><span class="pln">colors</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span><span class="pln">

    right</span><span class="pun">.</span><span class="pln">getChildren</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">button</span><span class="pun">);</span><span class="pln">    </span><span class="com">// أضف زرًا إلى الحاوية</span><span class="pln">
    colorMenu</span><span class="pun">.</span><span class="pln">getItems</span><span class="pun">().</span><span class="pln">add</span><span class="pun">(</span><span class="pln">menuItem</span><span class="pun">);</span><span class="pln"> </span><span class="com">// أضف عنصر قائمة إلى القائمة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
        menuItem</span><span class="pun">.</span><span class="pln">setSelected</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

colorGroup</span><span class="pun">.</span><span class="pln">selectedToggleProperty</span><span class="pun">().</span><span class="pln">addListener</span><span class="pun">(</span><span class="pln"> e </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// 2</span><span class="pln">
    </span><span class="typ">Toggle</span><span class="pln"> t </span><span class="pun">=</span><span class="pln"> colorGroup</span><span class="pun">.</span><span class="pln">getSelectedToggle</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">t </span><span class="pun">!=</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// 3</span><span class="pln">
        </span><span class="typ">Color</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Color</span><span class="pun">)</span><span class="pln">t</span><span class="pun">.</span><span class="pln">getUserData</span><span class="pun">();</span><span class="pln">
        message</span><span class="pun">.</span><span class="pln">setTextFill</span><span class="pun">(</span><span class="pln">c</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1] تعني: لاحِظ استخدام <code>UserData</code> لتخزين الكائن المُمثِّل للون عنصر القائمة لحاجتنا إليه لاحقًا.
	</li>
	<li>
		[2] تعني: استمع إلى التغييرات الواقعة بالخاصية <code>selectedToggleProperty</code> المُعرَّفة بالصنف <code>ToggleGroup</code>؛ لكي تتمكَّن من ضبط لون العنوان بما يتوافق مع عنصر القائمة الواقع عليه الاختيار.
	</li>
	<li>
		[3] بمعنى: يُمثِّل <code>t</code> العنصر المُختار من النوع <code>RadioMenuItem</code>، إذ يمكنك اِسترجاع اللون من <code>UserData</code> واستخدامه لضبط لون النص. ربما تُصبِح قيمة <code>selectedToggleProperty</code> فارغةً بلحظة معينة، لأن أحدهما وقع عليه الاختيار والآخر غير مختار.
	</li>
</ul>
<p>
	تُضاف عناصر القائمة إلى كائنٍ من النوع <code>ToggleGroup</code> -ألقِ نظرةً على مقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1148/" rel="">مكونات التحكم البسيطة في واجهة المستخدم في مكتبة جافا إف إكس JavaFX</a>-، بينما لا تُضاف أزرار الانتقاء إليه. والآن، إذا نقر المُستخدِم على إحدى أزرار الانتقاء التي لم تكن قيد الاختيار، فستتغير حالة عنصر القائمة المكافئ إلى وضع الاختيار، ولكن قبل أن يحدث ذلك، سيُغير كائن النوع <code>ToggleGroup</code> حالة عنصر القائمة المختار حاليًا إلى وضع عدم الاختيار، وهو ما يؤدي إلى تغيير حالة زر الانتقاء المربوط بعنصر القائمة ذاك إلى وضع عدم الاختيار أيضًا، وبالنهاية، ستتغير حالة كلٍ من زري الانتقاء وعنصري القائمة.
</p>

<p>
	سيكون من الأفضل لو فحصت طريقة استخدام البرنامج لخاصية <code>userData</code> المُعرَّفة بأزرار الانتقاء، على الرغم من أنها لا تتعلَّق بالارتباط ثنائي الاتجاه. تَحمِل كل عقدة node بمبيان المشهد بيانات المُستخدِم ضمن خاصية اسمها <code>userData</code> من النوع <code>Object</code>. لا يَستخدِم النظام تلك الخاصية المُعرَّفة بكل عقدة، ولذلك تُعدّ مكانًا مناسبًا لتخزين بعض البيانات المتعلقة بالعقدة، ويُسمَح لتلك البيانات بأن تكون من أي نوع. بالنسبة لهذا البرنامج، ستتكوَّن بيانات المُستخدِم بالعقدة المُمثِلة لعنصر زر انتقاء بالقائمة من قيمةٍ من النوع <code>Color</code>، وسيُستخدَم هذا اللون عند اختيار عنصر القائمة ذاك.
</p>

<p>
	نتمنى أن تكون الأمثلة التي تعرَّضنا لها بهذا المقال قد أقنعتك بفعالية ارتباط الخاصيات ودورها في تنفيذ العمليات بين الكائنات ببرامج مكتبة JavaFX. قد يبدو هذا الأسلوب من البرمجة غريبًا بالبداية، ولكنه أسهل وأكثر وضوحًا من التعامل مع الأحداث ومستمعي الأحداث مباشرةً.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="https://math.hws.edu/javanotes/c13/s1.html" rel="external nofollow">Section 1: Properties and Bindings</a> من فصل Chapter 13: GUI Programming Continued من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1487/" rel="">أمثلة برمجية على الشبكات في جافا: إطار عمل لتطوير الألعاب عبر الشبكة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/" rel="">مدخل إلى أساسيات البرمجة بلغة Java: ما هي البرمجة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">البواني وتهيئة الكائنات Object Initialization في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1525</guid><pubDate>Sat, 02 Apr 2022 15:00:00 +0000</pubDate></item><item><title>&#x623;&#x645;&#x62B;&#x644;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629; &#x639;&#x644;&#x649; &#x627;&#x644;&#x634;&#x628;&#x643;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;: &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x644;&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x639;&#x628;&#x631; &#x627;&#x644;&#x634;&#x628;&#x643;&#x629;</title><link>https://academy.hsoub.com/programming/java/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1487/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/6210b2523a187_---------.png.c01ebcab3a66d6b05903531949d2d5ae.png" /></p>

<p>
	سنناقش بهذا المقال مجموعةً من البرامج التي تَستخدِم الشبكات <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">والخيوط</a>. تتشارك تلك التطبيقات بمشكلة توفير الاتصال الشبكي بين مجموعةٍ من البرامج المُشغَّلة على حواسيبٍ مختلفة. تُعدّ الألعاب ثنائية اللاعبين أو متعددة اللاعبين عبر الشبكة واحدةً من الأمثلة النموذجية على هذا النوع من التطبيقات، ولكن تضطّر تطبيقاتٌ أخرى أكثر جدية لمواجهة نفس المشكلة أيضًا. سندرس بالقسم الأول من هذا المقال إطار عملٍ framework يُمكِن اِستخدامه ببرامج مختلفة تندرج تحت هذا النوع، ثم سنناقش بالجزء المتبقي من هذا المقال ثلاثة تطبيقات مبنيةً على هذا الإطار. في الواقع، قد تكون تلك التطبيقات هي الأكثر تعقيدًا بهذه السلسلة، ولذلك فهمها ليس ضروريًا لفهم أساسيات الشبكات.
</p>

<p>
	في الواقع، يعود الفضل لهذا المقال إلى الطالبين Alexander Kittelberger و Kieran Koehnlein؛ فقد أرادا أن يَكتُبا برنامج لعبة بوكر عبر الشبكة مشروعًا نهائيًا بالصف الذي كان الكاتب يُدرسّه. لقد ساعدهما الكاتب بالجزء المُتعلّق بالشبكات بكتابة إطار عملٍ بسيط يدعم الاتصال بين اللاعبين. يتعرَّض التطبيق إلى الكثير من الأفكار الهامة، ولذلك تقررت إضافة إصدارٍ أعم وأكثر تطورًا من إطار العمل إلى السلسلة. يمثّل المثال الأخير بهذا المقال برنامج لعبة بوكر عبر <a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-%D9%88%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-network-architecture-r484/" rel="">الشبكة</a>.
</p>

<h2>
	إطار عمل Netgame
</h2>

<p>
	تشترك جميع الألعاب المختلفة عبر الشبكة بشيءٍ واحد فيما يتعلّق بالشبكات على الأقل؛ حيث لا بُدّ من وجود طريقةٍ لإيصال الأفعال التي يفعلها لاعبٌ معينٌ إلى اللاعبين الآخرين عبر الشبكة. لذلك، سيكون من الأفضل لو أتحنا تلك الإمكانيات بقاعدةٍ مشتركة قابلةٍ لإعادة الاستخدام بواسطة الكثير من الألعاب المختلفة. على سبيل المثال، تحتوي حزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/" rel="external nofollow">netgame.common</a>، التي طورها الكاتب على الكثير من الأصناف.
</p>

<p>
	لم نتعامل كثيرًا مع الحزم packages بهذه السلسلة بخلاف استخدام الأصناف المبنية مسبقًا built-in. كنا قد شرحنا ماهية الحزم بمقال <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-programming-environment-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1020/" rel="">بيئات البرمجة programming environment في جافا</a>، ولكننا لم نَستخدِم بجميع الأمثلة البرمجية حتى الآن سوى الحزمة الافتراضية default package. تُستخدَم الحزم عمليًا بجميع المشروعات البرمجية بهدف تقسيم الشيفرة إلى مجموعةٍ من الأصناف المرتبطة، ولذلك كان من البديهي تعريف إطار العمل القابل لإعادة الاستخدام بحزمةٍ يُمكِن إضافتها مثل مجموعة متكاملة unit إلى أي مشروع.
</p>

<p>
	تُسهِّل بيئات التطوير المتكاملة Integrated development environments مثل Eclipse استخدام الحزم packages: فمن أجل استخدام حزمة <code>netgame</code> ضمن مشروع باِستخدَام إحدى بيئات التطوير المتكاملة، كل ما عليك فعله هو نَسْخ مجلد <a href="http://math.hws.edu/javanotes/source/chapter12/netgame" rel="external nofollow">netgame</a> بالكامل إلى المشروع؛ ونظرًا لاستخدام حزمة <code>netgame</code> <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B9%D8%B6-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A7%D9%84%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r1146/" rel="">مكتبة JavaFX</a>، ينبغي ضبط المشروع ببيئة Eclipse ليدعم <a href="https://academy.hsoub.com/programming/java/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1150/" rel="">استخدام مكتبة JavaFX</a>.
</p>

<p>
	إذا كنت تَعمَل بسطر الأوامر، ينبغي أن يحتوي مجلد العمل working directory على المجلد netgame بهيئة مجلدٍ فرعي. إذا لم تكن تَستخدِم إصدارًا قديمًا من JDK يتضمَّن مكتبة JavaFX مسبقًا، فينبغى أن تُضيف خيار JavaFX إلى أوامر <code>javac</code> و <code>java</code>. لنفترض أننا عرَّفنا الأمرين <code>jfxc</code> و <code>jfx</code> ليكافئا الأمرين <code>javac</code> و <code>java</code> بعد إضافة خيارات مكتبة JavaFX إليهما. والآن، إذا أردنا أن نُصرِّف كل ملفات جافا المُعرَّفة بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/" rel="external nofollow">netgame.common</a> مثلًا، يُمكِننا استخدام الأمر التالي في نظامي Mac OS و <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">Linux</a>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_14" style="">
<span class="pln">jfxc netgame</span><span class="pun">/</span><span class="pln">common</span><span class="com">/*.java</span></pre>

<p>
	أما بالنسبة لنظام Windows، يُمكِننا استخدام الشرطة المائلة للخلف بدلًا من الشرطة المائلة للأمام كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_16" style="">
<span class="pln">jfxc netgame\common\*</span><span class="pun">.</span><span class="pln">java</span></pre>

<p>
	بالنسبة لإصدارات JDK التي تتضمَّن بالفعل مكتبة JavaFX، يمكن استخدام <code>javac</code> وليس <code>jfxc</code>. ستحتاج إلى أوامرٍ مشابهة لتتمكَّن من تصريف الشيفرة المصدرية لبعض الأمثلة ضمن هذا المقال، وستجدها معرَّفةً ضمن حزمٍ فرعية subpackages أخرى من حزمة <code>netgame</code>.
</p>

<p>
	لتتمكَّن من تشغيل البرنامج <code>main</code> المُعَّرف بحزمةٍ معينة، ينبغي أن تكون بمجلدٍ يتضمَّن تلك الحزمة بهيئة مجلدٍ فرعي، كما ينبغي استخدام الاسم الكامل للصنف الذي تريد تشغيله. إذا أردت مثلًا تشغيل الصنف <code>ChatRoomWindow</code> -سيُناقش لاحقًا ضمن هذا المقال- المُعرَّف بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/chat/" rel="external nofollow">netgame.chat</a>، شغِّل الأمر التالي:
</p>

<pre class="ipsCode">
jfx netgame.chat.ChatRoomWindow
</pre>

<p>
	تُعدّ التطبيقات التي سنناقشها ضمن هذا المقال أمثلةً على البرمجة الموزَّعة؛ فهي تتضمَّن عدة حواسيب تتواصل عبر الشبكة. تَستخدِم تلك التطبيقات كما هو الحال في مثال قسم الحوسبة الموزعة من مقال <a href="http://xn--https-cdhh4euc/academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1486/" rel="external nofollow">المقال السابق</a> خادمًا server مركزيًا أو برنامجًا رئيسيًا master مُوصَّلًا بمجموعةٍ من العملاء clients؛ حيث تمر جميع الرسائل عبر <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a>، أي لا يستطيع عميلٌ معينٌ إرسال رسالةٍ إلى عميلٍ آخر مباشرةً. سنشير إلى الخادم ضمن هذا المقال باسم "الموزّع hub" بمعنى موزع الاتصالات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="92287" href="https://academy.hsoub.com/uploads/monthly_2022_02/001Hub_And_Clients.png.cefc0db30ad63c0ffd30ecbad3abb9d5.png" rel=""><img alt="001Hub_And_Clients.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92287" data-unique="vrz7r1ddw" src="https://academy.hsoub.com/uploads/monthly_2022_02/001Hub_And_Clients.png.cefc0db30ad63c0ffd30ecbad3abb9d5.png"></a>
</p>

<p>
	هناك عدة أشياء ينبغي أن تفهمها جيدًا: لا بُدّ أن يكون الموزع مُشغَّلًا قبل بدء تشغيل أيٍّ من العملاء. يتصل العملاء بالموزع ليتمكَّنوا من إرسال رسائلهم إليه، ويُعالِج الموزع جميع رسائل العملاء واحدةً تلو الأخرى بنفس ترتيب استقبالها، وبناءً على تلك المعالجة، يُمكِنه أن يرسل عدة رسائلٍ إلى عميلٍ واحدٍ أو أكثر. يَملُك كل عميل مُعرِّف هوية ID خاصٍ به، ويُمثِل ذلك إطار عمل framework عام يُمكِن اِستخدامه بمختلف أنواع التطبيقات، بحيث يُعرِّف كل تطبيق ما يخصُّه من رسائلٍ ومعالجات. لنتعمَّق الآن بتفاصيل البرنامج.
</p>

<p>
	كانت الرسائل في قسم الحوسبة الموزعة من المقال السابق تُرسَل جيئةً وذهابًا بين الخادم والعميل وفقًا لمتتاليةٍ مُحدَّدةٍ ومُعرَّفةٍ مُسبقًا، حيث كان الاتصال بين الخادم والعميل بمثابة اتصالٍ بين خيطٍ واحدٍ مُشغَّلٍ ببرنامج الخادم وخيطٍ آخرٍ مُشغّلٍ ببرنامج العميل. بالنسبة لإطار عمل <code>netgame</code>، نريد أن نجعل الاتصال غير متزامن asynchronous؛ بمعنى أننا لا نريد انتظار وصول الرسائل بناءً على متتاليةٍ متوقَّعة مسبقًا، ولجعل هذا ممكنًا، يَستخدِم العميل وفقًا لإطار عمل <code>netgame</code> خيطين للاتصال: الأول لإرسال الرسائل إلى الموزع، والآخر لاستقبال الرسائل منه. وبالمثل، يَستخدِم الموزع وفقًا لإطار عمل <code>netgame</code> خيطين للاتصال مع كل عميل.
</p>

<p>
	يكون الموزع عمومًا متصلًا مع عدة عملاء، ويستطيع استقبال الرسائل من أي عميل بأي وقت. ينبغي أن يُعالِج الموزع الرسائل بطريقةٍ ما، ويَستخدِم لإجراء تلك المعالجة خيط اتصالٍ واحد فقط لمعالجة جميع الرسائل. عندما يَستقبِل ذلك الخيط رسالةً معينةً من عميلٍ ما، فإنه يُدْخِلها إلى رتلٍ queue يحتوي على الرسائل المُستقبَلة؛ حيث يتوفَّر رتل واحد فقط تُخزِّن به رسائل جميع العملاء. في المقابل، يُنفِّذ خيط معالجة الرسائل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%AA%D9%84-blocks-%D9%88%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A7%D8%AA-loops-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D8%B9%D8%A7%D8%AA-branches-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1062/" rel="">حلقة تكرار loop</a> يقرأ خلالها رسالةً واحدةً من الرتل ويُعالجها، ثم يقرأ رسالةً أخرى ويُعالجها وهكذا. لاحِظ أن الرتل هو كائن من النوع <code>LinkedBlockingQueue</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="92288" href="https://academy.hsoub.com/uploads/monthly_2022_02/002Hub_And_Client_Threads.png.d58a4c4ecc7854739328cfffcbb900a5.png" rel=""><img alt="002Hub_And_Client_Threads.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92288" data-unique="ebbqsw9mv" src="https://academy.hsoub.com/uploads/monthly_2022_02/002Hub_And_Client_Threads.png.d58a4c4ecc7854739328cfffcbb900a5.png"></a>
</p>

<p>
	يتضمَّن الموزع خيطًا آخرًا غير مُوضَّحٍ بالصورة السابقة؛ حيث يُنشِئ ذلك الخيط مقبسًا من النوع <code>ServerSocket</code>، ويَستخدِمه للاستماع لطلبات الاتصال الآتية من العملاء. يُسلّم الموزع كل طلب اتصالٍ يَستقبِله إلى كائنٍ آخر من النوع المُتداخِل <code>ConnectionToClient</code>؛ حيث يُعالِج ذلك الكائن الاتصال مع العميل. يَملُك كل عميلٍ متصلٍ رقم مُعرِّف هويةٍ خاصٍ به، حيث تُسنَد مُعرِّفات الهوية 1 و2 و3 .. إلى العملاء عند اتصالهم؛ ونظرًا لأن بإمكانهم غلق الاتصال، قد لا تكون أرقام مُعرِّفات الهوية الخاصة بالعملاء المتصلين متتاليةً ضمن لحظةٍ معينة. يُستخدَم متغيرٌ من النوع <code>TreeMap&lt;Integer,ConnectionToClient&gt;‎</code> لربط أرقام مُعرِّفات الهوية الخاصة بالعملاء المتصلين مع الكائنات المسؤولة عن معالجة اتصالاتهم.
</p>

<p>
	تمثِّل الرسائل المُرسَلة والمُستقبَلة كائنات، ولذلك اِستخدَمنا مجاري دْخَل وخَرْج I/O streams من النوع <code>ObjectInputStream</code> و <code>ObjectOutputStream</code> لقراءة الكائنات وكتابتها. (انظر قسم إدخال وإخراج الكائنات المسلسلة من مقال <a href="https://academy.hsoub.com/programming/java/%D9%82%D9%86%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D8%AE%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%B1%D8%AC-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%AA%D9%8A-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%88%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1451/" rel="">قنوات الدخل والخرج وعمليتي القراءة والكتابة في جافا</a>). لقد غلَّفنا مجرى الخرج الخاص بالمقبس باستخدام الصنف <code>ObjectOutputStream</code> لنَسمَح بنقل الكائنات عبر المقبس؛ في حين غلَّفنا مجرى الدخل الخاص بالمقبس باستخدام الصنف <code>ObjectInputStream</code> لنَسمَح باستقبال الكائنات. ملاحظة: لا بُدّ أن تُنفِّذ الكائنات المُستخدَمة مع هذا النوع من المجاري الواجهة <code>java.io.Serializable</code>.
</p>

<p>
	ستَجِد الصنف <code>Hub</code> مُعرَّفًا بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/Hub.java" rel="external nofollow">Hub.java</a> في حزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/" rel="external nofollow">netgame.common</a>. يجب تخصيص رقم المنفذ الذي سيستمع إليه مقبس الخادم بهيئة معامل يُمرَّر إلى الباني constructor. يُعرِّف الصنف <code>Hub</code> التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_22" style="">
<span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> messageReceived</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> playerID</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">)</span></pre>

<p>
	عندما تصِل رسالةٌ من عميلٍ معينٍ إلى مقدمة رتل الرسائل، يقرأها خيط معالجة الرسائل من الرتل، ويَستدعِي ذلك التابع. بتلك اللحظة فقط، تبدأ المعالجة الفعلية لرسالة العميل.
</p>

<p>
	يُمثِّل المعامل الأول <code>playerID</code> رقم مُعرِّف الهوية الخاص بالعميل المُرسِل للرسالة؛ بينما يُمثِّل المعامل الثاني الرسالة نفسها. يُمرِّر التابع نسخةً من تلك الرسالة إلى جميع العملاء المتصلين، ويُمثِّل ذلك المعالجة الافتراضية التي يُجريها الموزع على الرسائل المُستقبَلة؛ ولكي يُنفِّذ ذلك، يُغلِّف الموزع أولًا كُلًا من رقم مُعرِّف الهوية <code>playerID</code> ومحتويات الرسالة <code>message</code> بكائنٍ من النوع <code>ForwardedMessage</code> (مُعرَّفٌ بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/ForwardedMessage.java" rel="external nofollow">ForwardedMessage.java</a> بحزمة <code>netgame.common</code>).
</p>

<p>
	قد تكون المعالجة الافتراضية مناسبة تمامًا لاحتياجات تطبيقٍ بسيطٍ، مثل برنامج غرفة المحادثة الذي سنناقشه لاحقًا، ولكن سنضطّر إلى تعريف صنفٍ فرعي subclass من الصنف <code>Hub</code> بالنسبة لغالبية التطبيقات، وإعادة تعريف التابع <code>messageReceived()‎</code> به لإجراء معالجةٍ أكثر تعقيدًا. في الواقع، يحتوي الصنف <code>Hub</code> على عدة توابعٍ أخرى، والتي قد تحتاج إلى إعادة تعريفها أيضًا. نستعرِض بعضًا منها فيما يلي:
</p>

<ul>
<li>
		<code>protected void playerConnected(int playerID)‎</code>: يُستدعَى ذلك التابع في كل مرةٍ يتصل خلالها لاعبٌ بالموزع؛ حيث يُمثِّل المعامل <code>playerID</code> رقم مُعرِّف الهوية الخاص باللاعب الجديد. لا يفعَل هذا التابع شيئًا بالصنف <code>Hub</code>. (لقد أرسَلَ الموزع رسالةً من النوع <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/StatusMessage.java" rel="external nofollow">StatusMessage</a> بالفعل إلى كل عميل ليخبره بوجود لاعبٍ جديد؛ أما الغرض من التابع <code>playerConnected()‎</code> فهو إجراء أي عملياتٍ أخرى إضافية قد ترغب الأصناف الفرعية المُشتقَة من الصنف <code>Hub</code> بتنفيذها). من الممكن الوصول إلى قائمة مُعرِّفات الهوية لجميع اللاعبين المتصلين حاليًا باستدعاء التابع <code>getPlayerList</code>.
	</li>
	<li>
		<code>protected void playerDisconnected(int playerID)‎</code>: يُستدعَى ذلك التابع في كل مرةٍ يُغلِق خلالها لاعبٌ معينٌ اتصاله مع الموزع (بعد أن يُرسِل الموزع رسالةً من النوع <code>StatusMessage</code> إلى العملاء). يُمثِّل المعامل رقم مُعرِّف الهوية الخاص باللاعب الذي أغلق الاتصال. لا يفعَل هذا التابع شيئًا بالصنف <code>Hub</code>.
	</li>
</ul>
<p>
	يُعرِّف الصنف <code>Hub</code> بعض التوابع العامة public المفيدة منها:
</p>

<ul>
<li>
		<code>sendToAll(message)‎</code>: يُرسِل ذلك التابع الرسالة <code>message</code> المُمرَّرة لكل عميلٍ متصل بالموزع حاليًا. لا بُدّ أن تكون الرسالة كائنًا غير فارغ يُنفِّذ الواجهة <code>Serializable</code>.
	</li>
	<li>
		<code>sendToOne(recipientID,message)‎</code>: يُرسِل ذلك التابع الرسالة <code>message</code> المُمرَّرة إلى مُستخدِمٍ واحدٍ فقط؛ حيث يُمثِّل المعامل الأول <code>recipientID</code> رقم مُعرِّف الهوية الخاص بالعميل الذي ينبغي أن يَستقبِل تلك الرسالة، ويُعيد التابع قيمة من النوع <code>boolean</code> تُساوِي <code>false</code> في حالة عدم وجود عميلٍ مقابلٍ لقيمة <code>recipientID</code>.
	</li>
	<li>
		<code>shutDownServerSocket()‎</code>: يُغلِق هذا التابع مقبس الخادم الخاص بالموزع؛ لكي لا يتمكَّن أي عميلٍ آخر من الاتصال. يُمكِننا اِستخدَامه بالألعاب الثنائية (لاعبين فقط) بعد أن يتصِل اللاعب الثاني مثلًا.
	</li>
	<li>
		<code>setAutoreset(autoreset)‎</code>: يضبُط قيمة خاصية <code>autoreset</code>؛ فإذا كانت قيمتها تساوي <code>true</code>، يُعَاد ضبط المجاري -من النوع <code>ObjectOutputStreams</code>- المُستخدَمة لنقل الرسائل إلى العملاء أوتوماتيكيًا قبل نقل تلك الرسائل. القيمة الافتراضية لتلك الخاصية هي <code>false</code>. يكون لإعادة ضبط مجرًى من النوع <code>ObjectOutputStream</code> معنًى إذا كان هناك كائنٌ قد كُتِبَ بالمجرى بالفعل، ثم عُدّل، ثم كُتِبَ إلى المجرى مرةً أخرى. إذا لم نُعِد ضبط المجرى قبل كتابة الكائن المُعدَّل به، تُرسَل القيمة القديمة غير المُعدَّلة إلى المجرى بدلًا من القيمة الجديدة. في تلك الحالة، يُفضَّل اِستخدَام كائناتٍ ثابتة غير قابلة للتعديل immutable مع الاتصال، ولا تكون عندها إعادة الضبط ضرورية.
	</li>
</ul>
<p>
	ينبغي أن تقرأ الشيفرة المصدرية للملف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/Hub.java" rel="external nofollow">Hub.java</a> للإطلاع على طريقة تنفيذ كل ما سبق وللمزيد من المعلومات في العموم. مع قليلٍ من الجهد والدراسة، ستكون قادرًا على فهم كل شيء موجود ضمن ذلك الملف، ومع ذلك تحتاج فقط إلى فهم الواجهة العامة public والمحمية protected بالصنف <code>Hub</code> والأصناف الأخرى المُعرَّفة بإطار عمل <code>netgame</code> حتى تتمكَّن من كتابة بعض التطبيقات المبنية عليها.
</p>

<p>
	لننتقل الآن إلى جانب العميل، ستَجِد تعريف الصنف المُمثِل للعميل بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/Client.java" rel="external nofollow">Client.java</a> بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/" rel="external nofollow">netgame.common</a>؛ حيث يحتوي الصنف على باني كائن constructor يَستقبِل كُلًا من اسم أو عنوان بروتوكول الإنترنت، ورقم المنفذ الخاصين بالموزع الذي سيتصل به العميل. يُسبِّب هذا <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">الباني</a> تعطيلًا إلى أن يُنشَأ الاتصال.
</p>

<p>
	الصنف <code>Client</code> هو صنفٌ مجرَّد abstract؛ أي لا بُدّ لأي تطبيق netgame أن يُعرِّف صنفًا فرعيًا مُشتقًا من الصنف <code>Client</code>، وأن يُوفِّر تعريفًا للتابع المُجرّد abstract التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_25" style="">
<span class="pln">abstract </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> messageReceived</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">);</span></pre>

<p>
	يُستدعَى التابع السابق بكل مرةٍ يَستقبِل خلالها العميل رسالةً من الموزع. قد يُعيد الصنف الفرعي المُشتق تعريف override التوابع المحمية الآتية:
</p>

<ul>
<li>
		<code>playerConnected</code>.
	</li>
	<li>
		<code>playerDisconnected</code>.
	</li>
	<li>
		<code>serverShutdown</code>.
	</li>
	<li>
		<code>connectionClosedByError</code>.
	</li>
</ul>
<p>
	انظر <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/Client.java" rel="external nofollow">الشيفرة المصدرية</a> لمزيدٍ من المعلومات. علاوةً على ذلك، يحتوي الصنف <code>Client</code> على متغير نسخة instance variable، اسمه <code>connectedPlayerIDs</code> من نوع المصفوفة <code>int[]‎</code>. تُمثِّل تلك المصفوفة قائمة أرقام مُعرِّفات الهوية الخاصة بجميع العملاء المتصلين حاليًا بالموزع. يُعرِّف الصنف <code>Client</code> مجموعةً من التوابع العامة، وسنستعرِض بعضًا من أهمها فيما يلي:
</p>

<ul>
<li>
		<code>send(message)‎</code>: ينقل هذا التابع رسالةً إلى الموزع. قد يكون المعامل <code>message</code> أي كائنٍ غير فارغ بشرط أن يُنفِّذ الواجهة <code>Serializable</code>.
	</li>
	<li>
		<code>getID()‎</code>: يسترجع هذا التابع رقم مُعرِّف الهوية الذي أسنده الموزع إلى العميل.
	</li>
	<li>
		<code>disconnect()‎</code>: يغلِق اتصال العميل مع الموزع. لاحظ أن العميل لن يتمكَّن من إرسال أي رسائلٍ أخرى بعد غلق الاتصال. إذا حاول العميل فعل ذلك، سيُبلِّغ التابع <code>send()‎</code> عن <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%AA-exceptions-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1068/" rel="">استثناءٍ</a> من النوع <code>IllegalStateException</code>.
	</li>
</ul>
<p>
	صُممّ الصنفان <code>Hub</code> و <code>Client</code> عمومًا ليُوفِّرا إطار عملٍ عام يُمكِن استخدامه أساسًا لكثيرٍ من الألعاب الشبكية المختلفة والبرامج المُوزَّعة أيضًا. جميع التفاصيل منخفضة المستوى المتعلقة بالاتصالات الشبكية والخيوط المتعدّدة مخفيةٌ ضمن الأقسام الخاصة private المُعرَّفة بتلك الأصناف، وتتمكَّن بذلك التطبيقات المبنية على تلك الأصناف من العمل وفقًا لمصطلحاتٍ عالية المستوى مثل اللاعبين والرسائل.
</p>

<p>
	صُممت تلك الأصناف على عدة مراحل بناءً على الخبرة المُكتسبَة من مجموعةٍ من التطبيقات الحقيقية، ولهذا يُفضَّل إلقاء نظرةٍ على الشيفرة المصدرية لرؤية طريقة استخدام الصنفين <code>Hub</code> و <code>Client</code> للخيوط والمقابس ومجاري الدْخَل والخرج. سنمرّ سريعًا بالجزء المُتبقِي من هذا المقال على ثلاثة تطبيقات مبنيةٍ على إطار عمل <code>netgame</code> وبدون مناقشة التفاصيل. يُمكِنك الإطلاع على الشيفرة المصدرية للتطبيقات الثلاثة كاملةً بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/" rel="external nofollow">netgame</a>.
</p>

<h2>
	تطبيق غرفة محادثة بسيط
</h2>

<p>
	تطبيقنا الشبكي الأول هو "غرفة محادثة" تُمكِّن المُستخدمين من الاتصال بخادمٍ معين، ومن ثم إرسال الرسائل؛ حيث يستطيع المُستخدمون المتواجدون بنفس الغرفة رؤية تلك الرسائل. يتشابه هذا التطبيق مع البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/GUIChat.java" rel="external nofollow">GUIChat</a> من قسم برنامج محادثة عبر الشبكة غير متزامن من مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1486/" rel="">الخيوط Threads والشبكات في جافا</a> باستثناء أنه من الممكن لأي عددٍ من المُستخدِمين التواجد بالمحادثة. لا يُعدّ هذا التطبيق لعبة، ولكنه يظهر الوظائف الأساسية التي يُوفِّرها إطار عمل <code>netgame</code>.
</p>

<p>
	يتكوَّن تطبيق غرفة المحادثة من برنامجين: الأول هو <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/chat/ChatRoomServer.java" rel="external nofollow">ChatRoomServer.java</a>، وهو في الواقع برنامجٌ بسيطٌ للغاية، فكل ما يفعله هو إنشاء موزع <code>Hub</code> يَستمِع إلى طلبات الاتصال القادمة من عملاء netgame:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_27" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Hub</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't create listening socket.  Shutting down."</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُعرَّف رقم المنفذ <code>PORT</code> على أنه ثابتٌ بالبرنامج، ويمكن أن يكون أي رقمٍ عشوائي شرط أن يَستخدِم الخادم والعملاء نفس الرقم. يَستخدِم البرنامج <code>ChatRoom</code> الصنف <code>Hub</code> نفسه لا صنفًا فرعيًا منه.
</p>

<p>
	أما البرنامج الثاني من تطبيق غرفة المحادثة فهو البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/chat/ChatRoomWindow.java" rel="external nofollow">ChatRoomWindow.java</a>، الذي ينبغي أن يُشغِّله المُستخدمون الذين يريدون المشاركة بغرفة المحادثة. يجب أن يعرِف المُستخدِم اسم أو عنوان بروتوكول الانترنت الخاص بالحاسوب الذي يَعمَل عليه الموزع. (لأغراض اختبار البرنامج، يُمكِنك تشغيل برنامج العميل بنفس الحاسوب الذي يَعمَل عليه الموزع باستخدام <code>localhost</code> اسمًا لحاسوب الموزع).
</p>

<p>
	يَعرِض <code>ChatRoomWindow</code> عند تشغيله صندوق نافذة ليطلب من المُستخدِم إدخال تلك المعلومات، ثم يَفتَح نافذةً تُمثِّل واجهة المُستخدِم لغرفة المحادثة؛ حيث تحتوي تلك النافذة على مساحةٍ نصيةٍ كبيرة لعرض الرسائل التي يُرسِلها المُستخدمون إلى الغرفة؛ كما تحتوي على صندوق إدخال نصي حيث يَستطيع المُستخدِم إدخال الرسائل. عندما يُدْخِل المُستخدِم رسالة، تظهر الرسالة بالمساحة النصية الموجودة بنافذة كل مُستخدِم مُتصِّلٍ بالموزع، ولذلك يرى المستخدمين جميع الرسائل المُرسَلة بواسطة أي مُستخدِم. لنفحص الآن طريقة برمجة ذلك.
</p>

<p>
	لابُدّ أن تُعرِّف تطبيقات netgame صنفًا فرعيًا مشتقًا من الصنف المجرّد <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/Client.java" rel="external nofollow">Client</a>. بالنسبة لتطبيق غرفة المحادثة، عرَّفنا العملاء بواسطة الصنف المتداخل <code>ChatClient</code> داخل البرنامج <code>ChatRoomWindow</code>. يُعرِّف البرنامج متغير النسخة <code>connection</code> من النوع <code>ChatClient</code>، والذي يُمثِّل اتصال البرنامج مع الموزع. عندما يُدْخِل المُستخدِم رسالة، تُرسَل الرسالة إلى الموزع باستدعاء التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_29" style="">
<span class="pln">connection</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span></pre>

<p>
	عندما يَستقبِل الموزع رسالة، فإنه يُغلِّفها ضمن كائنٍ من النوع <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/common/ForwardedMessage.java" rel="external nofollow">ForwardedMessage</a> مع إضافة رقم مُعرِّف الهوية الخاص بالعميل المُرسِل للرسالة. بعد ذلك، يُرسِل الموزع نسخةً من هذا الكائن إلى كل عميلٍ متصلٍ بالموزع، بما في ذلك العميل الذي أرسل الرسالة. من الجهة الأخرى، عندما يَستقبِل العميل رسالةً من الموزع، يُستدعَى التابع <code>messageReceived()‎</code> المُعرَّف بالكائن المنتمي إلى الصنف <code>ChatClient</code>؛ ويُعيد الصنف <code>ChatClient</code> تعريف ذلك التابع لكي يجعله يضيف الرسالة إلى المساحة النصية ببرنامج <code>ChatClientWindow</code>.
</p>

<p>
	اختصارًا لما سبق، تُرسَل أي رسالةٍ يُدْخِلها أي مُستخدِم إلى الموزع بينما يُرسِل الموزع نُسخًا من أي رسالةٍ يَستقبِلها إلى كل عميل، ولذلك يتسلَّم جميع العملاء نفس مجرى الرسائل من الموزع. بالإضافة إلى ما سبق، يُنبَّه كل عميلٍ عندما يَتصِل لاعبٌ جديدٌ بالموزع أو عندما يُغلِق لاعبٌ معينٌ اتصاله مع الموزع، وكذلك عندما يَفقِد هو نفسه اتصاله مع الموزع. يعيد البرنامج <code>ChatClient</code> تعريف التوابع التي تُستدعَى عند وقوع تلك الأحداث ليتمكَّن من إضافة رسائلٍ مناسبة إلى المساحة النصية. نستعرِض فيما يلي تعريف صنف العميل الخاص بتطبيق غرفة المحادثة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_31" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ChatClient</span><span class="pln"> extends </span><span class="typ">Client</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="com">// 2</span><span class="pln">
    </span><span class="typ">ChatClient</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> host</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">IOException</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="pln">host</span><span class="pun">,</span><span class="pln"> PORT</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">//3</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> messageReceived</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">message instanceof </span><span class="typ">ForwardedMessage</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  
                  </span><span class="com">// لا يوجد أنواع رسائلٍ أخرى متوقعة</span><span class="pln">
            </span><span class="typ">ForwardedMessage</span><span class="pln"> bm </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">ForwardedMessage</span><span class="pun">)</span><span class="pln">message</span><span class="pun">;</span><span class="pln">
            addToTranscript</span><span class="pun">(</span><span class="str">"#"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> bm</span><span class="pun">.</span><span class="pln">senderID </span><span class="pun">+</span><span class="pln"> </span><span class="str">" SAYS:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> bm</span><span class="pun">.</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 4</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> connectionClosedByError</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        addToTranscript</span><span class="pun">(</span><span class="pln">
           </span><span class="str">"Sorry, communication has shut down due to an error:\n     "</span><span class="pln"> 
                                     </span><span class="pun">+</span><span class="pln"> message </span><span class="pun">);</span><span class="pln">
        </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            sendButton</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
            messageInput</span><span class="pun">.</span><span class="pln">setEditable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
            messageInput</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
            messageInput</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">""</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
        connected </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
        connection </span><span class="pun">=</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 5</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> playerConnected</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> newPlayerID</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        addToTranscript</span><span class="pun">(</span><span class="pln">
                </span><span class="str">"Someone new has joined the chat room, with ID number "</span><span class="pln"> 
                </span><span class="pun">+</span><span class="pln"> newPlayerID </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 6</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> playerDisconnected</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> departingPlayerID</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        addToTranscript</span><span class="pun">(</span><span class="pln"> </span><span class="str">"The person with ID number "</span><span class="pln"> 
                            </span><span class="pun">+</span><span class="pln"> departingPlayerID </span><span class="pun">+</span><span class="pln"> </span><span class="str">" has left the chat room"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end nested class ChatClient</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1] يتصِل برنامج <code>ChatClient</code> مع الموزع ويُستخدَم لإرسال الرسائل واستقبالها من وإلى الموزع. تكون الرسائل المُستقبَلة من الموزع من النوع <code>ForwardedMessage</code> وتحتوي على رقم مُعرِّف الهوية الخاص بالمُرسِل وعلى <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">السلسلة النصية</a> التي أرسلها المُستخدِم.
	</li>
	<li>
		[2] يُنشِئ اتصالًا مع خادم غرفة المحادثة بالحاسوب المُخصَّص.
	</li>
	<li>
		[3] يُنفَّذ هذا التابع عند استقبال رسالةٍ من الخادم. ينبغي أن تكون الرسالة من النوع <code>ForwardedMessage</code>، وتُمثِّل شيئًا أرسله أحد المُستخدِمين المتواجدين بغرفة المحادثة. تُضَاف الرسالة ببساطة إلى الشاشة مع رقم مُعرِّف الهوية الخاص بالمُرسِل.
	</li>
	<li>
		[4] يُستدعَى هذا التابع عندما يُغلَق الاتصال مع عميلٍ ما نتيجةً لحدوث خطأ ما (يحدث ذلك عند غلق الخادم).
	</li>
	<li>
		[5] يُظهِر رسالةً على الشاشة عندما ينضم شخصٌ ما إلى غرفة المحادثة.
	</li>
	<li>
		[6] يُظهر رسالةً على الشاشة عندما يغادر شخصٌ ما غرفة المحادثة.
	</li>
</ul>
<p>
	يُمكِنك الإطلاع على ملفات الشيفرة المصدرية الخاصة بتطبيق غرفة المحادثة من حزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/chat/" rel="external nofollow">netgame.chat</a>.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: يُسنِد الموزع بتطبيق غرفة المحادثة رقم مُعرِّف هوية لكل مُستخدِم عند اتصاله، ولكن ما يزال المُستخدمون مجهولين، وقد لا يكون ذلك مناسبًا. ألقِ نظرةً على تمرين 12.7 بنهاية هذا المقال حيث ناقشنا طريقةً لمعالجة تلك المشكلة.
		</p>
	</div>
</blockquote>

<h2>
	لعبة إكس-أو عبر الشبكة
</h2>

<p>
	تطبيقنا الثاني سيكون لعبةً بسيطةً للغاية: لعبة إكس-أو الشهيرة؛ حيث يضع لاعبان بتلك اللعبة علاماتٍ بلوحةٍ مكوَّنة من 3 صفوف و3 أعمدة. يلعب أحدهما الرمز X بينما يلعب الآخر O، ويكون الهدف هو الحصول على 3 رموز X أو 3 رموز O بصفٍ واحد.
</p>

<p>
	تتكوَّن حالة لعبة إكس-أو بأي لحظة من مجموعةٍ من المعلومات، مثل مكونات اللوحة الحالية؛ واللاعب الذي حان دوره؛ وفي حال انتهت اللعبة، فمن الفائز ومن الخاسر. إذا لم نكن نُطوّر برنامجًا شبكيًا، كان بإمكاننا اِستخدَام متغيرات نسخة لتمثيل حالة اللعبة بحيث يستعين بها البرنامج لتحديد طريقة رسم اللوحة وطريقة الاستجابة لأفعال المُستخدِم مثل نقرات الفأرة، ولكن نظرًا لأننا نُطوِّر برنامجًا شبكيًا من اللعبة، سنَستخدِم ثلاثة <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-objects-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-methods-%D9%88%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-instance-variables-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1108/" rel="">كائنات</a>؛ بحيث ينتمي اثنان منهما إلى صنف العميل الذي يُوفِّر واجهة المُستخدِمين للعبة بالإضافة إلى الكائن المُمثِل للموزع والمسؤول عن إدارة الاتصالات مع العملاء. لا تتواجد تلك الكائنات بنفس الحاسوب، وبالتالي لا يُمكِنهم بالتأكيد اِستخدَام نفس متغيرات الحالة؛ ومع ذلك هناك حالةٌ واحدةٌ مُحدَّدة للعبة بأي لحظة، وينبغي أن يكون اللاعبان على درايةٍ بتلك الحالة.
</p>

<p>
	يُمكِننا حل تلك المشكلة بتخزين الحالة "الرسمية" للعبة بالموزع، وإرسال نسخةٍ منها إلى كل لاعب كلما تغيرت. لا يستطيع اللاعبان تعديل الحالة مباشرةً، فعندما يُنفِّذ لاعبٌ فِعلًا معينًا مثل وضع قطعةٍ على اللوحة، يُرسَل ذلك الفعِل إلى الموزع بهيئة رسالة. بعد ذلك، يُعدِّل الموزع حالة اللعبة لكي تَعكس نتيجة ذلك الفعل، ثم يُرسِل الحالة الجديدة لكلا اللاعبين، وعندها تُحدَّث نافذتهما لتعكس الحالة الجديدة. يُمكِننا بتلك الطريقة ضمَان ظهور اللعبة بنفس الحالة دائمًا عند كلا اللاعبين. بدلًا من إرسال نسخةٍ كاملةٍ من الحالة بكل مرةٍ تُعدَّل فيها، قد نُرسِل ذلك التعديل فقط. ولكن، سيتطلَّب ذلك استخدام طريقةٍ ما لتشفير التعديلات وتحويلها إلى رسائلٍ يُمكِن إرسالها عبر الشبكة. نظرًا لأن الحالة بهذا البرنامج بسيطةٌ للغاية، فإنه من الأسهل إرسالها كاملة.
</p>

<p>
	ستَجِد البرنامج إكس-أو مُعرَّفًا بعدة أصناف بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/" rel="external nofollow">netgame.tictactoe</a>؛ حيث يُمثِّل الصنف <a href="https://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/TicTacToeGameState.java" rel="external nofollow">TicTacToeGameState</a> حالة اللعبة، ويتضمَّن التابع التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_35" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> applyMessage</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> senderID</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">)</span></pre>

<p>
	يُعدِّل ذلك التابع حالة اللعبة لتَعكِس تأثير الرسالة المُرسَلة من إحدى اللاعبين؛ حيث تُمثِّل تلك الرسالة فعِلًا معينًا أقدم عليه اللاعب، مثل النقر على اللوحة.
</p>

<p>
	لا يَعرِف الصنف <code>Hub</code> أي شيءٍ عن تطبيق إكس-أو، ولأن الموزع بهذا التطبيق ينبغي أن يحتفظ بحالة اللعبة، كان من الضروري تعريف صنفٍ فرعي مشتقٍ من الصنف <code>Hub</code>. عرَّفنا الصنف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/TicTacToeGameHub.java" rel="external nofollow">TicTacToeGameHub</a>، وهو صنفٌ بسيطٌ للغاية يُعيد تعريف التابع <code>messageReceived()‎</code> ليجعله يَستجيب لرسائل اللاعبين بتطبيقها على حالة اللعبة ثم إرسال نسخةٍ من الحالة الجديدة لكلا اللاعبين. يُعيد ذلك الصنف تعريف التابعين <code>playerConnected()‎</code> و <code>playerDisconnected()‎</code> أيضًا لكي يُنفِّذا الفعِل المناسب؛ لأن المباراة تُلعَب فقط عندما يكون هناك لاعبين متصلين. انظر الشيفرة المصدرية كاملة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_37" style="">
<span class="pln">package netgame</span><span class="pun">.</span><span class="pln">tictactoe</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.</span><span class="typ">IOException</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> netgame</span><span class="pun">.</span><span class="pln">common</span><span class="pun">.</span><span class="typ">Hub</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">TicTacToeGameHub</span><span class="pln"> extends </span><span class="typ">Hub</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">TicTacToeGameState</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">  </span><span class="com">// يسجّل حالة اللعبة</span><span class="pln">

    </span><span class="com">// 2</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">TicTacToeGameHub</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> port</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">IOException</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="pln">port</span><span class="pun">);</span><span class="pln">
        state </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">TicTacToeGameState</span><span class="pun">();</span><span class="pln">
        setAutoreset</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 3</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> messageReceived</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> playerID</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        state</span><span class="pun">.</span><span class="pln">applyMessage</span><span class="pun">(</span><span class="pln">playerID</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">);</span><span class="pln">
        sendToAll</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 4</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> playerConnected</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> playerID</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">getPlayerList</span><span class="pun">().</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            shutdownServerSocket</span><span class="pun">();</span><span class="pln">
            state</span><span class="pun">.</span><span class="pln">startFirstGame</span><span class="pun">();</span><span class="pln">
            sendToAll</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// 5</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> playerDisconnected</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> playerID</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        state</span><span class="pun">.</span><span class="pln">playerDisconnected </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
        sendToAll</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1]: يُمثِّل الموزع hub باللعبة إكس-أو. هناك موزعٌ واحدٌ فقط باللعبة، ويتصِل كلا اللاعبين بنفس الموزع، الذي تتواجد بد المعلومات الرسمية عن حالة اللعبة؛ وعند حدوث تغييرات بتلك الحالة، يُرسِل الموزع الحالة الجديدة لكلا اللاعبين ليتأكّد من ظهور نفس الحالة لكليهما.
	</li>
	<li>
		[2]: يُنشِئ موزعًا يَستمِع إلى رقم المنفذ المُخصَّص. يَستدعِي التابع <code>setAutoreset(true)‎</code> لكي يتأكّد من إعادة ضبط مجرى الخرج الخاص بكل عميل تلقائيًا قبل إرسال أي رسالة. يُعدّ ذلك ضروريًا لأن نفس الكائن المُمثِل للحالة يُرسَل مرارً وتكرارً مع بعض التعديلات. يُمثِّل <code>port</code> رقم المنفذ الذي سيستمِع إليه الموزع. قد يُبلِّغ التابع عن <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%A7%D8%AA-exceptions-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1309/" rel="">استثناءِ</a> من النوع <code>IOException</code> في حالة عدم التمكُّن من فتح اتصالٍ برقم المنفذ المُخصَّص.
	</li>
	<li>
		[3]: يُنفَّذ هذا التابع عند وصول رسالةٍ من عميل؛ حيث تُطبَّق الرسالة عندئذٍ على حالة اللعبة باستدعاء التابع <code>state.applyMessage()‎</code>. تُنقَل الحالة بعد ذلك إلى جميع اللاعبين المتصلين إذا كانت قد تغيرت.
	</li>
	<li>
		[4]: يُستدعَى هذا التابع عند اتصال لاعب؛ وإذا كان ذلك اللاعب هو اللاعب الثاني، يُغلَق مقبس الاستماع الخاص بالخادم (يُسمح فقط وجود لاعبين). بعد ذلك، تبدأ اللعبة الأولى وتُنقَل الحالة الجديدة إلى كلا اللاعبين.
	</li>
	<li>
		[5]: يُستدعَى هذا التابع عندما يغلق لاعبٌ اتصاله. يُنهِي ذلك اللعبة ويتسبَّب بإغلاقها عند اللاعب الآخر أيضًا. يحدث ذلك بضبط قيمة <code>state.playerDisconnected</code> إلى <code>true</code> ثم إرسال الحالة الجديدة إلى اللاعب الآخر إذا كان موجودًا لتبليغه بأن اللعبة قد انتهت.
	</li>
</ul>
<p>
	يُمثَّل الصنف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/TicTacToeWindow.java" rel="external nofollow">TicTacToeWindow</a> واجهة اللاعب إلى اللعبة. كما حدث في تطبيق غرفة المحادثة، يُعرِّف ذلك الصنف صنفًا فرعيًا متداخلًا مُشتقًا من الصنف <code>Client</code> لتمثيل اتصال العميل مع الموزع. عندما تتغيّر حالة اللعبة، تُرسَل رسالةٌ إلى كل عميل، ويُستدعَى التابع <code>messageReceived()‎</code> الخاص بالعميل ليُعالِج تلك الرسالة. يستدعي ذلك التابع بدوره التابع <code>newState()‎</code> المُعرَّف بالصنف <code>TicTacToeWindow</code> لتحديث النافذة. اِستخدَمنا <code>Platform.runLater()‎</code> لكي نَستدعيه بخيط تطبيق JavaFX :
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5300_39" style="">
<span class="kwd">protected</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> messageReceived</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">message instanceof </span><span class="typ">TicTacToeGameState</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> newState</span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="typ">TicTacToeGameState</span><span class="pun">)</span><span class="pln">message </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	والآن، لنُشغِّل تطبيق إكس-أو، ينبغي أن يُشغِّل اللاعبان البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/Main.java" rel="external nofollow">Main.java</a> الموجود بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/tictactoe/" rel="external nofollow">netgame.tictactoe</a>، حيث يعرِض البرنامج نافذةً للمُستخدِم تُمكِّنه من اختيار بدء لعبة جديدة أو الانضمام إلى لعبةٍ موجودة.
</p>

<p>
	إذا بدأ المُستخدِم لعبةً جديدة، يُنشِئ البرنامج موزعًا من النوع <code>TicTacToeHub</code> لإدارة اللعبة، ويَعرِض نافذةً جديدةً من النوع <code>TicTacToeWindow</code>، وتكون متصلةً بالموزع على الفور. تبدأ اللعبة بمجرد اتصال لاعبٍ آخر بذلك الموزع. في المقابل، إذا اختار المُستخدِم الانضمام إلى لعبةٍ موجودة، لا يُنشِئ البرنامج موزعًا جديدًا، وإنما يُنشِئ نافذةً من النوع <code>TicTacToeWindow</code>، وتحاول تلك النافذة الاتصال بالموزع الذي أنشأه اللاعب الأول. ولذلك، لا بُدّ أن يَعرِف اللاعب الثاني اسم الحاسوب الذي يَعمَل عليه برنامج اللاعب الأول. إذا أردت اختبار البرنامج، يُمكِنك تشغيل كل شيء بنفس الحاسوب واستخدام "localhost" اسمًا للحاسوب.
</p>

<p>
	يُعدّ هذا البرنامج هو أول برنامج يَستخدِم نافذتين مختلفتين نراه. لاحِظ أن الصنف <code>TicTacToeWindow</code> مُعرَّف على أنه صنفٌ فرعي من الصنف <code>Stage</code> الذي يُستخدَم لتمثيل النوافذ بمكتبة JavaFX. تبدأ برامج <a href="https://academy.hsoub.com/programming/java/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A8%D8%B3%D9%8A%D8%B7-r1145/" rel="">JavaFX</a> "بمرحلة رئيسية primary stage" يُنشئِها النظام ويُمرِّرها معاملًا إلى التابع <code>start()‎</code>، ولكن يستطيع التطبيق إلى جانب ذلك إنشاء أي نوافذ إضافية.
</p>

<h2>
	لعبة بوكر Poker عبر الشبكة
</h2>

<p>
	ننتقل الآن إلى التطبيق الذي يُعدّ المُلهم لإطار عمل <code>netgame</code>، وهو تطبيق لعبة بوكر. بالتحديد، نفَّذنا نسخةً من النسخة التقليدية "سحب خمسة بطاقات five card draw" من تلك اللعبة وبلاعبين. هذا التطبيق معقدٌ نوعًا ما، ولن نناقشه تفصيليًا هنا، ولكننا سنوضِّح تصميمه العام. يُمكِنك الإطلاع على الشيفرة المصدرية كاملة بحزمة <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/" rel="external nofollow">netgame.fivecarddraw</a>. ينبغي أن تكون على درايةٍ بتلك النسخة من اللعبة لتتمكَّن من فهم البرنامج بالكامل.
</p>

<p>
	تتشابه لعبة Poker مع لعبة إكس-أو في العموم، حيث يتوفَّر الصنف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/Main.java" rel="external nofollow">Main</a> الذي يُشغِّله كلا اللاعبين. يبدأ اللاعب الأول لعبةً جديدة بينما ينضم اللاعب الثاني إلى اللعبة الموجودة. يُمثِّل الصنف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/PokerGameState.java" rel="external nofollow">PokerGameState</a> حالة اللعبة؛ بينما يدير الصنف الفرعي <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/PokerHub.java" rel="external nofollow">PokerHub</a> اللعبة.
</p>

<p>
	لعبة Poker أكثر تعقيدًا من لعبة إكس-أو، وهو ما ينعكس على حالة اللعبة، فهي معقدةٌ بالموازنة مع حالة لعبة إكس-أو، وبالتالي فإننا لا نرغب بنشر النسخة الجديدة من حالة اللعبة كاملةً إلى اللاعبين في كل مرة نُجرِي فيها تعديلًا صغيرًا على الحالة. علاوة على ذلك، لا معنى لأن يَعرِف اللاعبان حالة اللعبة بالكامل بما في ذلك بطاقات الخصم وبطاقات اللعب التي يَسحَب منها اللاعبان. لن تكون برامج العملاء مضطّرةً لعرض حالة اللعبة بالكامل للاعبين، ولكنهما قد يَستبدلا تلك البرامج ببرامجٍ أخرى ويتمكَّنا من الغش بسهولة. ولذلك، سيكون الموزع من النوع <code>PokerHub</code> فقط على درايةٍ بكامل حالة اللعبة بهذا التطبيق.
</p>

<p>
	سيُمثِّل كائنٌ من الصنف <code>PokerGameState</code> حالة اللعبة من وجهة نظر لاعبٍ واحدٍ فقط. عندما تتغير حالة اللعبة، سيُنشِئ الموزع كائنين مختلفين من النوع <code>PokerGameState</code> يُمثِّلان حالة اللعبة من وجهة نظر كل لاعب، ثم سيُرسِل الكائن المناسب لكل لاعب. يُمكِنك الإطلاع على <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/" rel="external nofollow">الشيفرة المصدرية</a> لمزيدٍ من التفاصيل.
</p>

<p>
	تُعدّ موازنة يد لاعبين لمعرفة أيهما أكبر الجزء الأصعب بلعبة البوكر، وقد عالجناها بهذا التطبيق بالصنف <a href="http://math.hws.edu/javanotes/source/chapter12/netgame/fivecarddraw/PokerRank.java" rel="external nofollow">PokerRank</a>. ربما تجده مفيدًا بألعاب بوكر أخرى.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c12/s5.html" rel="external nofollow">Section 5: Network Programming Example: A Networked Game Framework</a> من فصل Chapter 12: Threads and Multiprocessing من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1486/" rel="">الخيوط Threads والشبكات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D9%88%D8%B1%D9%82-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1111/" rel="">تطبيق عملي: بناء لعبة ورق في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1487</guid><pubDate>Tue, 29 Mar 2022 15:09:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62E;&#x64A;&#x648;&#x637; Threads &#x648;&#x627;&#x644;&#x634;&#x628;&#x643;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1486/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62109d4e07f9e_-.png.0e66efa46ac61da1311e99b26dfb9e11.png" /></p>

<p>
	ناقشنا بالمقال السابق عدة أمثلة على البرمجة الشبكية، حيث تعلّمنا طريقة إنشاء اتصالٍ شبكيٍ وطريقة تبادل المعلومات عبر تلك الاتصالات، ولكننا لم نتعامل مع واحدةٍ من أهم خاصيات البرمجة عبر <a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-%D9%88%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-network-architecture-r484/" rel="">الشبكة</a>، وهي حقيقة كون الاتصال الشبكي غير متزامن asynchronous. بالنسبة لبرنامج معين بأحد طرفي اتصال شبكي معين، قد تصِل الرسائل من الطرف الآخر بأي لحظة؛ أي يكون وصول رسالةٍ معينة حدثًا خارج نطاق تحكُّم البرنامج المُستقبِل للرسالة، ولذلك ربما تكون الاستعانة <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">بواجهة برمجة تطبيقات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> مبنيةٍ على الأحداث وخاصةٍ بالشبكات فكرةً جيدةً تُمكِّننا من التعامل مع الطبيعة غير المتزامنة للاتصالات عبر الشبكة؛ ولكن لا تعتمد جافا في الواقع على تلك الطريقة، حيث تَستخدِم برمجة الشبكات بلغة جافا <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">الخيوط threads</a> عمومًا.
</p>

<h2>
	مشكلة الدخل والخرج المعطل Blocking I/O
</h2>

<p>
	كما ناقشنا في مقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>، تَستخدِم برمجة الشبكات المقابس sockets، والتي تشير إلى أحد طرفي اتصال شبكي. يَملُك كل مقبس مجرى دْخَل input stream ومجرى خرج output stream، وتُنقَل البيانات المُرسَلة إلى مجرى الخرج بأحد طرفي الإتصال عبر الشبكة إلى مجرى الدْخَل بالطرف الآخر.
</p>

<p>
	إذا أراد برنامجٌ معينٌ قراءة البيانات من مجرى الدْخَل الخاص بالمقبس، فعليه استدعاء أحد توابع قراءة البيانات من مجرى الدْخَل. قد تكون البيانات قد وصلت بالفعل قبل استدعاء التابع، وفي تلك الحالة يَسترجِع التابع البيانات ويعيدها على الفور. ومع ذلك، قد يضطّر التابع لانتظار وصول البيانات من الطرف الآخر من الاتصال؛ أي يُعطَّل block كُلًا من التابع والخيط الذي استدعاه إلى أن تَصِل البيانات.
</p>

<p>
	قد يتعطَّل أيضًا تابع إرسال البيانات إلى مجرى الخرج الخاص بالمقبس؛ حيث يحدث ذلك إذا حاول البرنامج إرسال البيانات إلى المقبس بسرعةٍ أكبر من سرعة نقل البيانات عبر الشبكة. يَستخدِم المقبس مخزنًا مؤقتًا buffer لحمل البيانات المُفترَض نقلها عبر الشبكة. تذكَّر أن المخزن المؤقت هو مساحةٌ من الذاكرة تَعمَل بطريقةٍ مشابهةٍ للرتل queue. يضع تابع الخرج البيانات بالمخزن المؤقت، ثم يقرأها برنامج منخفض المستوى، ويُرسِلها عبر الشبكة. إذا كان المخزن ممتلئًا، يتعطَّل تابع الخرج إلى أن تتوفَّر مساحةٌ بالمخزن.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: عندما يعود تابع الخرج، لا يَعنِي ذلك أن البيانات قد أُرسِلَت، وإنما يَعنِي فقط أن البيانات قد وُضِعَت بالمخزن تمهيدًا لنقلها لاحقًا.
		</p>
	</div>
</blockquote>

<p>
	يَستخدِم الاتصال الشبكي دخلًا وخرجًا مُعطِّلًا؛ لأن عمليات الدْخَل والخَرْج عبر الشبكة قد تتسبَّب بحدوث تعطّيلٍ لفتراتٍ غير محددة، ولذلك لا بُدّ أن تكون البرامج المُستخدِمة للشبكات مهيأةً للتعامل مع ذلك التعطيل. في بعض الحالات، قد يكون إغلاق البرنامج لجميع العمليات الأخرى وانتظار البيانات المطلوبة مقبولًا، مثلما يحدث عندما يُحاوِل برنامج سطر أوامر قراءة البيانات التي ينبغي أن يُدْخِلها المُستخدِم.
</p>

<p>
	في الواقع، تُعدّ مدخلات المُستخدِم نوعًا من الدْخل والخَرْج المُعطِّل. ومع ذلك، تَسمَح الخيوط لبعض أجزاء البرنامج بالاستمرار بالعمل بينما بعض الأجزاء الأخرى مُعطَّلة. على سبيل المثال، ربما يكون من المناسب اِستخدَام خيطٍ واحدٍ مع برنامج عميل client شبكي يُرسِل طلباتٍ إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم server</a>، إذا لم يكن هناك أي شيءٍ ينبغي للبرنامج فعله بينما ينتظر رد الخادم. في المقابل، قد يتصل برنامج خادم شبكي مع عدة عملاء بنفس الوقت، وبالتالي بينما ينتظر الخادم وصول البيانات من العميل، سيكون لديه الكثير ليفعله خلال تلك الفترة، مثل الاتصال مع عملاءٍ آخرين. عندما يَستخدِم الخادم خيوطًا مختلفةً لمعالجة الاتصالات مع العملاء المختلفة، لن يؤدي التعطُّل الناتج عن عميلٍ معين إلى إيقاف تعامل الخادم مع العملاء الآخرين.
</p>

<p>
	يختلف استخدام الخيوط للتعامل مع مشكلة الدْخَل والخَرْج المعُطِّل جذريًا عن استخدامها لتسريع عملية المعالجة. عندما نَستخدِم الخيوط لزيادة سرعة المعالجة -كما ناقشنا بالقسم مجمع الخيوط وأرتال المهام من مقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">الخيوط threads والمعالجة على التوازي في جافا</a> السابق- كان من المنطقي استخدام خيطٍ واحدٍ لكل معالج. إذا كان الحاسوب يحتوي على معالج واحد، فلن يزيد اِستخدَام أكثر من خيط ن سرعة المعالجة إطلاقًا، بل قد يُبطئها نتيجةً للحمل الزائد overhead الناتج عن إنشاء الخيوط وإداراتها.
</p>

<p>
	من الجهة الأخرى، إذا كان الهدف هو حل مشكلة الدْخَل والخَرْج المُعطِّل، فسييكون من المنطقي اِستخدَام خيوطٍ بعددٍ أكبر مما يحتويه الحاسوب من معالجات؛ لأن كثيرًا من تلك الخيوط قد يتعطَّل بأي لحظة، وستتنافس بالتالي الخيوط غير المُعطَّلة فقط على زمن المعالجة. إذا أردنا تشغيل جميع المعالجات على الدوام، فلا بُدّ من وجود خيطٍ واحدٍ نشطٍ لكل معالج (في الواقع، أقل من ذلك بعض الشيء لنَسمَح بالاختلافات بين عدد الخيوط النشطة من فترةٍ لأخرى). تقضِي الخيوط ببرامج الخوادم مثلًا معظم الوقت مُعطَّلة بانتظار اكتمال عمليات الدخل والخرج؛ فإذا كانت الخيوط مُعطَّلةً بنسبة 90% من الوقت مثلًا، فسنحتاج إلى خيوطٍ بعددٍ يُقارِب عشرة أضعاف عدد المعالجات؛ أي تستطيع الخوادم الاستفادة من اِستخدَام عددٍ كبيرٍ من الخيوط حتى لو كان الحاسوب يحتوي على معالجٍ واحد.
</p>

<h2>
	برنامج محادثة عبر الشبكة غير متزامن
</h2>

<p>
	سنفحص الآن المثال الأول على استخدام الخيوط ببرامج الاتصال الشبكي، وسيكون برنامج محادثة <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">بواجهة مُستخدِم رسومية GUI</a>.
</p>

<p>
	اِستخدَمنا بروتوكولًا للاتصال في برامج المحادثة عبر سطر الأوامر <a href="http://math.hws.edu/javanotes/source/chapter11/CLChatClient.java" rel="external nofollow">CLChatClient.java</a> و <a href="http://math.hws.edu/javanotes/source/chapter11/CLChatServer.java" rel="external nofollow">CLChatServer.java</a> في مقال تواصل تطبيقات جافا عبر الشبكة المشار إليه بالأعلى؛ حيث بعدما يُدْخِل المُستخدِم بأحد طرفي الاتصال رسالةً، لا بُدّ له أن ينتظر ردًا من الطرف الآخر. سيكون من الأفضل لو أنشأنا برنامج محادثة غير متزامن asynchronous، حيث سيتمكَّن المُستخدِم من الاستمرار بكتابة الرسائل وإرسالها دون انتظار أي ردودٍ من الطرف الآخر، كما ستُعرَض الرسائل الآتية من الطرف الآخر بمجرد وصولها. في الواقع، من الصعب تنفيذ ذلك ببرنامج <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر أوامر</a>، ولكنه بديهي ببرنامجٍ ذي واجهة مُستخدِم رسومية.
</p>

<p>
	تتمثل فكرة البرنامج باختصار في إنشاء خيطٍ وظيفته قراءة الرسائل القادمة من الطرف الآخر من الاتصال، وتُعرَض للمُستخدِم بمجرد وصولها، ثم يُعطَّل خيط قراءة الرسائل إلى أن تَصِل رسائلٌ أخرى. تستطيع الخيوط الأخرى الاستمرار بالعمل بينما ذلك الخيط مُعطَّل؛ حيث سيتمكَّن خيط معالجة الأحداث events بالتحديد من الاستجابة لما يفعله المُستخدِم، وسيتمكَّن بالتالي من إرسال الرسائل بعد أن يكتبها المُستخدِم مباشرةً.
</p>

<p>
	يَعمَل برنامج "GUIChat" مثل عميلٍ أو خادم.و يحتوي البرنامج على زر "Listen"، وعندما ينقر عليه المُستخدِم، يُنشِئ البرنامج مقبس خادم يَستمِع إلى طلبات الاتصال القادمة، وبالتالي يَعمَل البرنامج خادمًا. يحتوي البرنامج أيضًا على زر "Connect"، وعندما يَنقُر عليه المُستخدِم، يُرسِل البرنامج طلب اتصال، وبالتالي يَعمَل البرنامج عميلًا. يَستمِع الخادم كما جرت العادة إلى رقم منفذٍ port number معين، ولا بُدّ أن يعرف العميل الحاسوب الذي يَعمَل عليه الخادم ورقم المنفذ الذي يَستمِع إليه. تحتوي نافذة البرنامج "GUIChat" على صناديق إدخال تَسمَح للمُستخدِم بإدخال تلك المعلومات.
</p>

<p>
	بمجرد بدء الاتصال بين برنامجي "GUIChat"، يستطيع المُستخدِم الموجود بأي طرفٍ من طرفي الاتصال إرسال الرسائل إلى المُستخدِم بالطرف الآخر. تحتوي النافذة على صندوق إدخالٍ يَسمَح للمُستخدِم بكتابة الرسائل، ثم النقر على زر العودة الى بداية السطر لإرسالها. يستجيب خيط معالجة الأحداث إلى الحدث الناشئ عن كتابة الرسالة بإرسالها. وفي المقابل، تُستقبَل الرسائل القادمة بخيطٍ منفصلٍ جُلّ ما يفعله هو انتظار الرسائل القادمة. يكون ذلك الخيط مُعطَّلًا أثناء انتظاره؛ وعند وصول رسالة، يَعرِضها للمُستخدِم. تحتوي نافذة البرنامج على مساحةٍ كبيرةٍ لِعرض الرسائل المُرسَلة والمُستقبَلة إلى جانب بعض المعلومات الأخرى عن الاتصال الشبكي.
</p>

<p>
	يُفضَّل أن تُصرِّف compile شيفرة البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/GUIChat.java" rel="external nofollow">GUIChat.java</a>، وتُجرِّبه. إذا أردت تجريبه على حاسوبٍ واحد، يُمكِنك تشغيل نسختين من البرنامج على نفس الحاسوب، وإنشاء الاتصال بين نافذتي البرنامج باستخدام "localhost" أو "127.0.0.1" على أنه اسمٌ للحاسوب. حاوِل أيضًا قراءة الشيفرة المصدرية للبرنامج. سنناقش بعضها فقط فيما يلي.
</p>

<p>
	يَستخدِم البرنامج الصنف المُتداخِل <code>ConnectionHandler</code> المُشتَق من الصنف <code>Thread</code> لمعالجة معظم المهام المُتعلّقة بالشبكة، حيث يتولى الخيط المُمثَّل بواسطة الصنف <code>ConnectionHandler</code> مهمة إنشاء الاتصال الشبكي، وقراءة الرسائل القادمة بعد فتح الاتصال. نضمَن عدم تعطُّل واجهة المُستخدِم الرسومية أثناء إنشاء الاتصال بوضعنا الشيفرة المسؤولة عن إنشاء الاتصال واستقبال الرسائل بخيطٍ منفصل. يُعدّ فتح الاتصال عمليةً مُعطِّلة، مثل عملية قراءة الرسائل، وقد تَستغرِق بعض الوقت.
</p>

<p>
	يُنشِئ الصنف <code>ConnectionHandler</code> الاتصال بكلا الحالتين؛ أي عندما يَعمَل البرنامج خادمًا أو عميلًا. يُنشَأ ذلك الخيط عندما ينقر المُستخدم على زر "Listen" أو زر "Connect"؛ حيث يؤدي زر "Listen" إلى عمل البرنامج على أنه خادم؛ بينما يؤدي زر"Connect" إلى عمله عميلًا. يُميّز الصنف <code>ConnectionHandler</code> بين تلك الحالتين بتعريف بانيين constructors معروضين بالأسفل. يَعرِض التابع <code>postMessage()‎</code> رسالةً بالمساحة النصية الموجودة بالنافذة لكي يراها المُستخدِم. ألقِ نظرةً على تعريف كلا البانيين:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_13" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="typ">ConnectionHandler</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> port</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// For acting as the "server."</span><span class="pln">
   state </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">LISTENING</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">port </span><span class="pun">=</span><span class="pln"> port</span><span class="pun">;</span><span class="pln">
   postMessage</span><span class="pun">(</span><span class="str">"\nLISTENING ON PORT "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> port </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> setDaemon</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
   start</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// 2</span><span class="pln">
</span><span class="typ">ConnectionHandler</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> remoteHost</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> port</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// For acting as "client."</span><span class="pln">
   state </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CONNECTING</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">remoteHost </span><span class="pun">=</span><span class="pln"> remoteHost</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">port </span><span class="pun">=</span><span class="pln"> port</span><span class="pun">;</span><span class="pln">
   postMessage</span><span class="pun">(</span><span class="str">"\nCONNECTING TO "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> remoteHost </span><span class="pun">+</span><span class="pln"> </span><span class="str">" ON PORT "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> port </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> setDaemon</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
   start</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1]: تعني اِستمِع إلى طلب اتصالٍ برقم منفَّذٍ مُخصَّص. لا يفعل الباني أي عملياتٍ شبكية، وإنما يضبُط فقط بعض متغيرات النسخ ويُشغِّل الخيط. يَستمِع الخيط إلى طلب اتصالٍ واحدٍ فقط، ثم يَغلِق مقبس الخادم.
	</li>
	<li>
		[2]: تعني  اِفتح اتصال مع الحاسوب برقم المنفَّذ المُخصَّص. لا يفعل الباني أي عمليات شبكية، وإنما يضبُط فقط بعض متغيرات النسخ ويُشغِّل الخيط.
	</li>
</ul>
<p>
	لاحِظ أن <code>state</code> هو متغير نسخة instance variable من نوع التعداد enumerated type التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_15" style="">
<span class="kwd">enum</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> LISTENING</span><span class="pun">,</span><span class="pln"> CONNECTING</span><span class="pun">,</span><span class="pln"> CONNECTED</span><span class="pun">,</span><span class="pln"> CLOSED </span><span class="pun">};</span></pre>

<p>
	تُمثِّل قيم ذلك التعداد الحالات المختلفة للاتصال الشبكي. يُفضَّل غالبًا التعامل مع الاتصال الشبكي مثل آلة حالة state machine (انظر قسم آلات الحالة من مقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1147/" rel="">تعرف على أهم الأحداث والتعامل معها في مكتبة جافا إف إكس JavaFX</a>)؛ نظرًا لاعتماد الاستجابة إلى الأحداث المختلفة عادةً على الحالة التي كان عليها الاتصال عند وقوع الحدث. يُحدِّد الخيط ما إذا كان عليه التصرف مثل خادمٍ أو عميل أثناء إنشاء الاتصال من خلال ضبط قيمة المتغير <code>state</code> إلى القيمة <code>LISTENING</code> أو <code>CONNECTING</code>.
</p>

<p>
	بمجرد بدء تشغيل الخيط، يُنفَّذ التابع <code>run()‎</code> المُعرَّف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_18" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">LISTENING</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// أنشِئ اتصالًا بصفة خادم</span><span class="pln">
         listener </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="pln">port</span><span class="pun">);</span><span class="pln">
         socket </span><span class="pun">=</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln">
         listener</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CONNECTING</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// أنشِئ اتصالًا بصفة عميل</span><span class="pln">
         socket </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Socket</span><span class="pun">(</span><span class="pln">remoteHost</span><span class="pun">,</span><span class="pln">port</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      connectionOpened</span><span class="pun">();</span><span class="pln">  </span><span class="com">// 2</span><span class="pln">
      </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CONNECTED</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// 3</span><span class="pln">
         </span><span class="typ">String</span><span class="pln"> input </span><span class="pun">=</span><span class="pln"> in</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">
         </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">input </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
            connectionClosedFromOtherSide</span><span class="pun">();</span><span class="pln"> </span><span class="com">// أغلق المقبس وبلِّغ المُستخدِم</span><span class="pln">
         </span><span class="kwd">else</span><span class="pln">
            received</span><span class="pun">(</span><span class="pln">input</span><span class="pun">);</span><span class="pln">  </span><span class="com">// بلِّغ الرسالة إلى المُستخدم</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="com">// 4</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">!=</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CLOSED</span><span class="pun">)</span><span class="pln">
         postMessage</span><span class="pun">(</span><span class="str">"\n\n ERROR:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   finally </span><span class="pun">{</span><span class="pln">  </span><span class="com">// Clean up before terminating the thread.</span><span class="pln">
      cleanUp</span><span class="pun">();</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1]: يُستدعَى التابع <code>run()‎</code> بواسطة الخيط. يَفتَح التابع اتصالًا مثل خادمٍ أو عميل بالاعتماد على نوع <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">الباني</a> المُستخدَم.
	</li>
	<li>
		[2]: جهِّز الاتصال بما في ذلك إنشاء كائن من الصنف <code>BufferedReader</code> لقراءة الرسائل القادمة.
	</li>
	<li>
		[3]: اقرأ سطرًا نصيًا واحدًا من الطرف الآخر من الاتصال وبلِّغه للمُستخدِم.
	</li>
	<li>
		[4]: حدث خطأ. بلِّغ المُستخدِم إذا لم يكن قد أغلق الاتصال، لأنه قد يكون الخطأ المُتوقَّع حدوثه عند غلق مقبس الاتصال.
	</li>
</ul>
<p>
	يَستدعِي هذا التابع مجموعةً من التوابع الأخرى لإنجاز بعض الأمور، ولكن بإمكانك فهم الفكرة العامة لطريقة عمله. بعد فتح الاتصال مثل خادمٍ أو عميل، يُنفِّذ التابع <code>run()‎</code> حلقة <code>while</code> يَستقبِل خلالها الرسائل من الطرف الآخر من الاتصال ويعالجها إلى أن يُغلَق الاتصال. من المهم فهم طريقة غلق الاتصال؛ حيث تُوفِّر نافذة البرنامج GUIChat الزر "Disconnect"، وعندما ينقر عليه المُستخدِم، يُغلَق الاتصال. يَستجِيب البرنامج إلى ذلك الحدث بغلق المقبس المُمثِّل للاتصال، وكذلك بضبط حالة الاتصال إلى <code>CLOSED</code>.
</p>

<p>
	في تلك الأثناء، قد يكون خيط معالجة الاتصال مُعطَّلًا بانتظار الرسالة التالية نتيجة لاستدعائه التابع <code>in.readLine()‎</code>. عندما يُغلِق خيط واجهة المُستخدِم الرسومية المقبس، يفشل ذلك التابع ويُبلِّغ عن <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%AA-exceptions-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1068/" rel="">استثناءٍ exception</a> يتسبَّب بانتهاء الخيط. إذا كان خيط معالجة الاتصال واقعًا بين لحظتي استدعاء التابع <code>in.readLine()‎</code> عند غلق المقبس، تنتهي حلقة <code>while</code> لأن حالة الاتصال تتبدَّل من <code>CONNECTED</code> إلى <code>CLOSED</code>. يؤدي غلق نافذة البرنامج إلى غلق الاتصال بنفس الطريقة.
</p>

<p>
	علاوةً على ذلك، قد يُغلَق الاتصال بواسطة المُستخدِم على الطرف الآخر، ويُغلَق في تلك الحالة مجرى الرسائل القادمة، ويُعيد التابع <code>in.readLine()‎</code> بهذا الطرف من الاتصال القيمة <code>null</code>، وهو ما يُشير إلى نهاية المجرى، ويُمثِّل إشارةً إلى أن المُستخدِم الآخر قد أغلق الاتصال.
</p>

<p>
	سنُلقِي نظرةً أخيرة على شيفرة البرنامج GUIChat وخاصةً على التوابع المسؤولة عن إرسال الرسائل واستقبالها. يَستدعِي الخيطان تلك التوابع؛ حيث يَستدعِي خيط معالجة الأحداث التابع <code>send()‎</code> بغرض إرسال رسالةٍ إلى المُستخدِم على الطرف الآخر من الاتصال استجابةً لفِعِل المُستخدِم. قد تؤدي عملية إرسال البيانات إلى حدوث تعطيل -وإن كان احتمال ذلك ضئيلًا- إذا أصبح المخزن المؤقت buffer الخاص بالمقبس ممتلئًا. ربما يأخذ برنامج أكثر تطورًا ذلك الاحتمال بالحسبان. يَستخدِم التابع كائنًا من النوع <code>PrintWriter</code>، اسمه هو <code>out</code>، لإرسال الخرج إلى مجرى الخرج الخاص بالمقبس. لاحِظ أن التابع متزامنٌ ليَمنَع تعديل حالة الاتصال أثناء إجراء عملية الإرسال. انظر شيفرة التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_22" style="">
<span class="com">// 1</span><span class="pln">
synchronized </span><span class="kwd">void</span><span class="pln"> send</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CONNECTED</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      postMessage</span><span class="pun">(</span><span class="str">"SEND:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> message</span><span class="pun">);</span><span class="pln">
      out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
      out</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">out</span><span class="pun">.</span><span class="pln">checkError</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         postMessage</span><span class="pun">(</span><span class="str">"\nERROR OCCURRED WHILE TRYING TO SEND DATA."</span><span class="pun">);</span><span class="pln">
         close</span><span class="pun">();</span><span class="pln">  </span><span class="com">// أغلِق الاتصال</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وتعني [1]: يُرسِل رسالةً إلى الطرف الآخر من الاتصال ويعرضها على الشاشة. ينبغي أن يُستدعَى هذا التابع عندما تكون حالة الاتصال <code>ConnectionState.CONNECTED</code> فقط. يتجاهل التابع الاستدعاء بالحالات الأخرى.
</p>

<p>
	يَستدعِي خيط معالجة الأحداث التابع <code>received()‎</code> بعد قراءة رسالةٍ من المُستخدِم على الطرف الآخر من الاتصال، حيث يعرِض التابع الرسالة للمُستخدِم. لاحِظ أن التابع متزامن لتجنُّب حالة التسابق race condition التي يُمكِنها أن تحدُث إذا بدَّل خيطٌ آخر حالة الاتصال بينما ما يزال التابع قيد التنفيذ. ألقِ نظرةً على شيفرة التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_24" style="">
<span class="com">// 1</span><span class="pln">
synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> received</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state </span><span class="pun">==</span><span class="pln"> </span><span class="typ">ConnectionState</span><span class="pun">.</span><span class="pln">CONNECTED</span><span class="pun">)</span><span class="pln">
      postMessage</span><span class="pun">(</span><span class="str">"RECEIVE:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> message</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	[1] يُستدعَى هذا التابع بواسطة التابع <code>run()‎</code> عند استقبال رسالةٍ من الطرف الآخر من الاتصال. يُظهِر التابع الرسالة على الشاشة إذا كانت حالة الاتصال <code>CONNECTED</code> فقط؛ لأنها قد تَصِل بعد أن ينقر المُستخدم على زر "Disconnect"، وبالتالي لا ينبغي أن يراها المُستخدِم.
</p>

<h2>
	خادم شبكي متعدد الخيوط
</h2>

<p>
	تُستخدَم الخيوط عادةً ببرامج الخوادم؛ لأنها تَسمَح للخادم بالتعامل مع مجموعةٍ من العملاء بنفس الوقت. عندما يبقى عميلٌ معينٌ متصلًا لفترةٍ طويلةٍ نسبيًا، لا ينبغي أن يضطّر العملاء الآخرون إلى انتظار الخدمة، وحتى إن كان من المتوقَّع أن يكون تعامل الخادم مع كل عميلٍ قصيرًا للغاية، حيث لا يُمكِننا أن نفترض أن ذلك سيكون هو الحال دائمًا؛ فقد يُسيء عميلٌ معينٌ التصرف، ويبقى متصلًا دون أن يُرسِل البيانات التي يتوقَّعها الخادم؛ لذلك ينبغي أن نأخذ تلك الاحتمالية بالحسبان، لأننا إن لم نفعل، فإنه قد يؤدي إلى غلق الخيط تمامًا. في المقابل، إذا اعتمد الخادم على الخيوط، سيكون هناك خيوطٌ أخرى بإمكانها الرد على العملاء الآخرين.
</p>

<p>
	يُعدّ المثال التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter11/DateServer.java" rel="external nofollow">DateServer.java</a> من مقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a> برنامج خادم بسيطٍ للغاية؛ فهو لا يَستخدِم الخيوط، ولذلك لا بُدّ أن ينتهي الخادم من اتصاله مع عميلٍ معين قبل أن يَقبَل اتصالًا من عميلٍ آخر. سنرى الآن كيفية تحويل ذلك البرنامج إلى خادمٍ مبني على الخيوط. في الواقع، يُعَد هذا الخادم بسيطًا جدًا لدرجة أن اِستخدَام الخيوط معه لا معنى له، ولكن الفكرة أنه من الممكن تطبيق نفس تلك التقنيات على الخوادم الأكثر تعقيدًا. ألقِ نظرةً على تمرين 12.5 على سبيل المثال.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: لا يحتاج برنامج العميل <a href="http://math.hws.edu/javanotes/source/chapter11/DateClient.java" rel="external nofollow">DateClient.java</a> المقابل لبرنامج الخادم إلى استخدام الخيوط؛ لأن العميل يَستخدِم اتصالًا واحدًا. سيَعمَل برنامج العميل الأصلي مع النسخة الجديدة من برنامج الخادم.
	</p>
</blockquote>

<p>
	لنناقش الآن المحاولة الأولى من البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/DateServerWithThreads.java" rel="external nofollow">DateServerWithThreads.java</a>، حيث يُنشِئ هذا البرنامج خيطًا جديدًا بكل مرة يَستقبِل خلالها طلب اتصال بدلًا من مُعالجة الاتصال بنفسه باستدعاء برنامجٍ فرعي subroutine. يُنشِئ البرنامج <code>main</code> الخيط ويُمرِّر إليه الاتصال. يَستغرِق ذلك وقتًا قصيرًا للغاية، والأهم أنه لن يتسبَّب بحدوث تعطيل. في المقابل، يُعالِج تابع الخيط <code>run()‎</code> الاتصال بنفس الكيفية التي عالجنا بها الاتصال بالبرنامج الأصلي. تُعد برمجة ذلك سهلة، ويمكنك إلقاء نظرةٍ على النسخة الجديدة من البرنامج بعد إجراء التعديلات؛ حيث يمكننا ملاحظة أن الباني constructor الخاص بخيط الاتصال لا يَفعَل شيئًا تقريبًا، ولا يُسبِّب تعطيلًا، وهذا أمرٌ مهم؛ لأنه يُنفَّذ بالخيط الرئيسي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_27" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">net</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">DateServerWithThreads</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> final </span><span class="typ">int</span><span class="pln"> LISTENING_PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">32007</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="typ">ServerSocket</span><span class="pln"> listener</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اِستمِع لطلبات الاتصال القادمة</span><span class="pln">
        </span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">;</span><span class="pln">      </span><span class="com">// للتواصل مع البرنامج المتصِل</span><span class="pln">

    </span><span class="com">// استمر باستقبال طلبات الاتصال ومعالجتها للأبد إلى أن يحدث خطأ ما</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            listener </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="pln">LISTENING_PORT</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Listening on port "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> LISTENING_PORT</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// اِقبَل طلب الاتصال التالي وأنشِئ خيطًا لمعالجته</span><span class="pln">
                connection </span><span class="pun">=</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln"> 
                </span><span class="typ">ConnectionHandler</span><span class="pln"> handler </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ConnectionHandler</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">);</span><span class="pln">
                handler</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Sorry, the server has shut down."</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end main()</span><span class="pln">


    </span><span class="com">// 2</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ConnectionHandler</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Socket</span><span class="pln"> client</span><span class="pun">;</span><span class="pln"> </span><span class="com">// يُمثِّل اتصالًا مع عميل</span><span class="pln">
        </span><span class="typ">ConnectionHandler</span><span class="pun">(</span><span class="typ">Socket</span><span class="pln"> socket</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            client </span><span class="pun">=</span><span class="pln"> socket</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                 </span><span class="com">// (code copied from the original DateServer program)</span><span class="pln">
            </span><span class="typ">String</span><span class="pln"> clientAddress </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">getInetAddress</span><span class="pun">().</span><span class="pln">toString</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Connection from "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> clientAddress </span><span class="pun">);</span><span class="pln">
                </span><span class="typ">Date</span><span class="pln"> now </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">();</span><span class="pln">  </span><span class="com">// التوقيت والتاريخ الحالي</span><span class="pln">
                </span><span class="typ">PrintWriter</span><span class="pln"> outgoing</span><span class="pun">;</span><span class="pln">   </span><span class="com">// مجرى لإرسال البيانات</span><span class="pln">
                outgoing </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">getOutputStream</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
                outgoing</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> now</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
                outgoing</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">  </span><span class="com">// تأكّد من إرسال البيانات</span><span class="pln">
                client</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">){</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error on connection with: "</span><span class="pln"> 
                        </span><span class="pun">+</span><span class="pln"> clientAddress </span><span class="pun">+</span><span class="pln"> </span><span class="str">": "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


</span><span class="pun">}</span><span class="pln"> </span><span class="com">//end class DateServerWithThreads</span></pre>

<p>
	إذ أن:
</p>

<ul>
<li>
		[1]: يمثّل هذا البرنامج خادمًا يَستقبِل طلبات الاتصال برقم المنفذ الذي يُخصِّصه الثابث LISTENING_PORT. يرسِل البرنامج عند فتح اتصال التوقيت الحالي إلى المقبس المتصِل، ويستمر باستقبال طلبات الاتصال ومعالجتها إلى أن يُغلَق بالضغط على <code>CONTROL-C</code> على سبيل المثال. تُنشِئ هذه النسخة من البرنامج خيطًا جديدًا لكل طلب اتصال.
	</li>
	<li>
		[2]: يُعرِّف خيطًا لمعالجة الاتصال مع عميلٍ واحد.
	</li>
</ul>
<p>
	انظر إلى نهاية التابع <code>run()‎</code>، حيث أضفنا <code>clientAddress</code> إلى رسالة الخطأ؛ لنتمكَّن من معرفة الاتصال الذي تشير إليه رسالة الخطأ. نظرًا لأن الخيوط تُنفَّذ على التوازي، قد يَختلِط خرج الخيوط المختلفة؛ أي ليس من الضروري أن تَظهَر الرسائل الخاصة بخيطٍ معين معًا، وإنما قد يَفصِل بينها رسائلٌ من خيوطٍ أخرى. يُمثِّل ذلك واحدًا فقط من ضمن التعقيدات الكثيرة التي ينبغي الانتباه لها عند تعاملنا مع الخيوط.
</p>

<h2>
	استخدام مجمع الخيوط
</h2>

<p>
	لا يُعدّ إنشاء خيطٍ جديدٍ لكل اتصال الحل الأفضل، خاصةً إذا كانت تلك الاتصالات قصيرة. لحسن الحظ، يُمكِننا أن نَستخدِم مُجمّع خيوط الذي ناقشناه بقسم مجمع الخيوط وأرتال المهام من المقال السابق.
</p>

<p>
	يُعدّ البرنامج <a href="https://math.hws.edu/javanotes/source/chapter12/DateServerWithThreadPool.java" rel="external nofollow">DateServerWithThreadPool.java</a> نسخةً مُحسنَّةً من الخادم؛ حيث يَستخدِم مجمع خيوط. يُنفِّذ كل خيطٍ ضمن ذلك المُجمّع <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%AA%D9%84-blocks-%D9%88%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A7%D8%AA-loops-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D8%B9%D8%A7%D8%AA-branches-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1062/" rel="">حلقة تكرار</a> لا نهائية infinite loop يُعالِج كل تكرارٍ منها اتصالًا. علينا أن نَجِد طريقةً تُمكِّن البرنامج <code>main</code> من إرسال الاتصالات إلى الخيوط. ومن البديهي أن نَستخدِم رتلًا مُعطِّلًا blocking queue لذلك الغرض، وسيكون اسمه هو <code>connectionQueue</code>. تقرأ خيوط معالجة الاتصال الاتصالات من الرتل؛ ونظرًا لكونه رتلًا مُعطِّلًا، تتعطَّل الخيوط عندما يكون الرتل فارغًا، وتستيقظ عند إتاحة اتصالٍ جديدٍ <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3-stack-%D9%88%D8%A7%D9%84%D8%B1%D8%AA%D9%84-queue-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF%D8%A9-adt-r1396/" rel="">بالرتل</a>. لن نحتاج إلى أي تقنيات مزامنة أو اتصال أخرى؛ فكل شيءٍ مبنيٌ مُسبقًا بالرتل المُعطِّل. تعرض الشيفرة التالية التابع <code>run()‎</code> الخاص بخيوط معالجة الاتصال:
</p>

<pre class="ipsCode">
public void run() {
    while (true) {
        Socket client;
        try {
            client = connectionQueue.take();  // تعطَّل إلى أن يُتاح عنصر جديد
        }
        catch (InterruptedException e) {
            continue; // إذا قُوطِعَ، عدّ إلى بداية حلقة التكرار
        }
        String clientAddress = client.getInetAddress().toString();
        try {
            System.out.println("Connection from " + clientAddress );
            System.out.println("Handled by thread " + this);
            Date now = new Date();  // التاريخ والتوقيت الحالي
            PrintWriter outgoing;   // مجرى لإرسال البيانات
            outgoing = new PrintWriter( client.getOutputStream() );
            outgoing.println( now.toString() );
            outgoing.flush();  // تأكّد من إرسال البيانات فعليًا
            client.close();
        }
        catch (Exception e){
            System.out.println("Error on connection with: " 
                    + clientAddress + ": " + e);
        }
    }
}
</pre>

<p>
	يُنفِّذ البرنامج <code>main()‎</code> حلقة تكرارٍ لا نهائية تَستقبِل الاتصالات وتُضيفها إلى الرتل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_33" style="">
<span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// اقبل طلب الاتصال التالي وأضِفه إلى الرتل</span><span class="pln">
    connection </span><span class="pun">=</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        connectionQueue</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">);</span><span class="pln"> </span><span class="com">// تعطَّل إذا كان الرتل ممتلئًا</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لاحِظ أن الرتل بهذا البرنامج من النوع <code>ArrayBlockingQueue&lt;Socket&gt;‎</code>؛ أي أن لديه سعةً قصوى، وبالتالي إذا كان الرتل ممتلئًا، سيؤدي تنفيذ عملية <code>put()‎</code> إلى حدوث تعطيل. ولكن ألسنا نريد تجنُّب حدوث أي تعطيلٍ بالبرنامج <code>main()‎</code>؟ لأنه إذا تعطُّل، فلن يستقبل الخادم أي اتصالاتٍ أخرى، ويضطّر العملاء الذي يحاولون الاتصال إلى الانتظار. أليس من الأفضل اِستخدام الصنف <code>LinkedBlockingQueue</code> بسعةٍ غير محدودة؟
</p>

<p>
	في الحقيقة، تُعدّ الاتصالات الموجودة بالرتل المُعطِّل قيد الانتظار على أية حال، أي أنها لم تُخدَّم بعد؛ وإذا ازداد حجم الرتل بدرجةٍ غير معقولة، فستضطّر الاتصالات الموجودة بالرتل إلى الانتظار إلى فتراتٍ غير معقولةٍ أيضًا. وبالتالي، إذا ازداد حجم الرتل إلى ما لانهاية، لا يَعنِي ذلك سوى أن الخادم يَستقبِل طلبات اتصال بسرعةٍ أكبر من سرعة قدرته على معالجة تلك الاتصالات.
</p>

<p>
	قد يحدث ذلك لعدة أسباب: ربما يكون خادمك غير قوي كفاية لمعالجة كمية الاتصالات المطلوبة، وينبغي في تلك الحالة أن تشتري خادمًا جديدًا؛ أو ربما بسبب عدم احتواء مجمع الخيوط على عددٍ كافٍ من الخيوط بدرجة تَسمَح بالاستفادة من إمكانيات الخادم بالكامل، وينبغي في تلك الحالة زيادة حجم مجمع الخيوط ليتوافق مع إمكانيات الخادم؛ أو ربما يتعرَّض الخادم لهجوم الحرمان من الخدمات Denial Of Service، أي أن هناك شخصٌ معينٌ يحاول متعمّدًا إرسال طلبات اتصال إلى الخادم بقدرٍ أكبر مما يَستطيع احتماله في محاولة لمنع العملاء من الوصول إلى الخدمة.
</p>

<p>
	في جميع الأحوال، يُعدّ الصنف <code>ArrayBlockingQueue</code> ذو السعة المحدودة هو الخيار الأصح. ينبغي أن يكون الرتل قصيرًا حتى لا تضطّر الإتصالات الموجودة به إلى الانتظار لفتراتٍ طويلة قبل وصولها إلى الخدمة. بالنسبة للخوادم الحقيقية، لا بُدّ من ضبط كُلٍ من حجم الرتل وعدد خيوط المجمع بما يتناسب مع الخادم؛ كما ينبغي أن يُؤخَذ العتاد والشبكة التي يَعمَل عليها الخادم بالحسبان؛ وكذلك طبيعة طلبات العملاء التي يُفترَض معالجتها، وهو ما يُمثِّل تحديًا صعبًا عمومًا.
</p>

<p>
	بالمناسبة، هناك احتماليةٌ أخرى قد تسوء الأمور نتيجة لها: لنفترض أن الخادم يحتاج إلى قراءة بعض البيانات من العميل، ولكن العميل لا يرسل أية بيانات. في تلك الحالة، قد يتعطَّل الخيط الذي يحاول قراءة البيانات إلى الأبد بانتظار المُدْخَلات. إذا كنا نَستخدِم مجمع خيوط، فقد يَحدُث ذلك لجميع الخيوط الموجودة بالمجمع، وعندها، لن تحدث أي معالجةٍ أخرى. يتمثل حل تلك المشكلة بإيقاف الاتصال إذا بقي غير نشطٍ لفترةٍ طويلة من الوقت.
</p>

<p>
	ينتبه كل خيط اتصال عادةً إلى آخر توقيت استقبل به بياناتٍ من العميل. يُشغِّل الخادم خيطًا آخر يُطلَق عليه أحيانًا اسم "خيط reaper" تيمُنًا بفرقة "Grim Reaper"، ويوقظه دوريًا؛ حتى يَفحَص خيوط الاتصال، ويرى فيما إذا كان هناك خيطٌ غير نشط لفترةٍ طويلة. إذا بقي خيطٌ قيد الانتظار لفترة طويلة، فإنه يُنهَى، ويحلّ محله خيطٌ جديد. تُمثِّل الإجابة على سؤال "كم من الوقت ينبغي أن تكون تلك الفترة الطويلة؟" تحديًا آخر.
</p>

<h2>
	الحوسبة الموزعة
</h2>

<p>
	لقد رأينا طريقة اِستخدام الخيوط لإجراء المعالجة على التوازي؛ حيث تَعمَل عدة معالجات معًا لإنجاز مهمةٍ معينة. كان افتراضنا حتى الآن هو أن جميع تلك المعالجات موجودةٌ ضمن حاسوبٍ واحد مُتعدِّد المعالجات، ولكن يُمكِن إجراء المعالجة على التوازي باستخدام معالجاتٍ موجودة بحواسيبٍ مختلفة شرط أن تكون تلك الحواسيب متصلةً عبر شبكةٍ معينة؛ ويُطلَق على هذا النوع من المعالجة المتوازية -أي أن تَعمَل عدة حواسيب معًا لإنجاز مهمةٍ عبر الشبكة- اسم <strong>الحوسبة المُوزَّعة distributed computing</strong>.
</p>

<p>
	يُمكِننا أن ننظر لشبكة الانترنت وكأنها نموذجٌ ضخمٌ لحوسبةٍ موزَّعة، ولكننا في الواقع معنيون بالكيفية التي يمكن أن تتعاون من خلالها الحواسيب المتصلة عبر شبكةٍ على حل مشكلة حوسبية. هناك طرائقٌ متعددة للحوسبة الموزّعة، وتدعم جافا بعضًا منها. على سبيل المثال، تُمكِّن كُلًا من تقنية استدعاء التوابع عن بعد Remote Method Invocation - RMI وتقنية كوربا Common Object Request Broker Architecture - CORBA برنامجًا مُشغَّلًا على حاسوبٍ معين من استدعاء توابع كائناتٍ موجودة بحواسيبٍ اخرى. يَسمَح ذلك بتصميم برنامجٍ كائني التوجه object-oriented تُنفَّذ أجزاءه المختلفة بحواسيب مختلفة. تدعم RMI الاتصال بين <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-objects-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-oop-r966/" rel="">كائنات جافا</a> فقط؛ بينما تُعدّ CORBA المعيار الأكثر عمومية، حيث تَسمَح للكائنات المكتوبة بأي لغة برمجية، بما في ذلك <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">جافا</a>، بالتواصل مع بعضها بعضًا.
</p>

<p>
	كما هو الحال مع الشبكات، لدينا مشكلة تحديد موقع الخدمات (المقصود بالخدمة هنا هو الكائن المُتاح للاستدعاء عبر الشبكة)، أي كيف يستطيع حاسوبٌ معين معرفة الحاسوب الذي تَعمَل عليه خدمةٌ معينة ورقم المنفذ الذي تَستمِع إليه؟ تحلّ تقنيتي "RMI" و "CORBA" تلك المشكلة باستخدام "وسيط طلب request broker"؛ حيث يمثّل ذلك الوسيط برنامج خادم يَعمَل بعنوانٍ معروف، ويحتوي على قائمة الخدمات المتاحة بالحواسيب الأخرى، وتُبلِّغ الحواسيب المُقدِّمَة لخدمات الوسيط بخدماتها. ينبغي أن تعرف الحواسيب التي تحتاج خدمةً معينةً عنوان الوسيط للتواصل معه وسؤاله عن الخدمات المُتاحة وعناوينها.
</p>

<p>
	تُعدّ "RMI" و "COBRA" أنظمةً معقدة وليس من السهل تمامًا استخدامها، وذُكرت هنا لأنها جزءٌ من <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-stream-api-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1433/" rel="">واجهة برمجة تطبيقات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> جافا</a> للشبكات القياسية، ولكننا لن نناقشها إلى أبعد من ذلك. بدلًا منها، سنفحص مثالًا أبسط على الحوسبة المُوزَّعة يَستخدِم أساسيات الشبكات فقط.
</p>

<p>
	يتشابه هذا المثال مع ذلك الذي اِستخدمناه بالبرنامج <a href="https://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo1.java" rel="external nofollow">MultiprocessingDemo1.java</a> ونُسخه المختلفة بالمقالين <a href="http://xn--https-cdhh4euc/academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/" rel="external nofollow">البرمجة باستخدام الخيوط threads في جافا</a> والمقال السابق، وهو مثال حساب ألوان صورةٍ معقدة، ولكننا لن نُنشِئ هنا برنامجًا بواجهة مُستخدِم رسومية، ولن نعرض الصورة على الشاشة. تَستخدِم عملية المعالجة أبسط أنواع المعالجة على التوازي بتقسيم المشكلة إلى عدة مهام يُمكِن تنفيذها باستقلالية دون الاتصال مع المهام الأخرى.
</p>

<p>
	لتطبيق الحوسبة المُوزَّعة على هذا النوع من المشكلات، سنَستخدِم برنامجًا رئيسيًا master يتولى مسؤولية تقسيم المشكلة إلى عدة مهام وإرسالها عبر الشبكة إلى البرامج العاملة worker لتُنفِّذها. ينبغي أن تُرسِل تلك البرامج نتائجها إلى البرنامج الرئيسي الذي يَدمِج نتائج جميع المهام معًا ليكوِّن حلًا لكامل المشكلة. يُطلَق على البرامج العاملة ضمن هذا السياق اسم "البرامج التابعة slaves"، ويُقال أن البرنامج يَستخدِم أسلوب "البرنامج الرئيسي والبرنامج التابع master/slave" لإجراء الحوسبة المُوزَّعة.
</p>

<p>
	يتكوَّن هذا البرنامج من ثلاثة ملفات؛ حيث يُعرِّف الملف <a href="https://math.hws.edu/javanotes/source/chapter12/CLMandelbrotMaster.java" rel="external nofollow">CLMandelbrotMaster.java</a> البرنامج الرئيسي، بينما يُعرِّف الملف <a href="https://math.hws.edu/javanotes/source/chapter12/CLMandelbrotWorker.java" rel="external nofollow">CLMandelbrotWorker.java</a> البرامج العاملة التابعة؛ وأخيرًا يُعرِّف الملف <a href="https://math.hws.edu/javanotes/source/chapter12/CLMandelbrotTask.java" rel="external nofollow">CLMandelbrotTask.java</a> الصنف <code>CLMandelbrotTask</code> الذي يُمثِّل المهمة التي تُنفِّذها البرامج العاملة.
</p>

<p>
	يُقسِّم البرنامج الرئيسي master المشكلة إلى عدة مهام، ثم يُوزِّعها على البرامج العاملة، التي تُنفِّذ تلك المهام، ثم تُعيد النتائج إلى البرنامج الرئيسي. ويُطبِّق البرنامج الرئيسي بالنهاية نتائج جميع المهام المفردة على المشكلة الأساسية.
</p>

<p>
	لتشغيل البرنامج، سنبدأ أولًا بتشغيل البرنامج "CLMandelbrotWorker" على عدّة حواسيب (ربما بتنفيذها بسطر الأوامر). يَستخدِم ذلك البرنامج الصنف <code>CLMandelbrotTask</code>، ولذلك يجب أن يكون كل من الملف <code>CLMandelbrotWorker.class</code> والملف <code>CLMandelbrotTask.class</code> متوفرين بالحواسيب المُمثِّلة للبرامج العاملة. بعد ذلك، سنُشغِّل البرنامج المسمى "CLMandelbrotMaster" على الحاسوب المُمثِل للبرنامج الرئيسي، مع ملاحظة أنه يحتاج إلى الصنف <code>CLMandelbrotTask</code> أيضًا.
</p>

<p>
	يجب أن نُمرِّر اسم أو <a href="https://academy.hsoub.com/questions/15089-%D8%AA%D8%AD%D8%AF%D9%8A%D8%AF-%D8%B9%D9%86%D9%88%D8%A7%D9%86-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%84-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-ip-address-%D9%81%D9%8A-nodejs/" rel="">عنوان بروتوكول الانترنت IP address</a> الخاص بجميع الحواسيب العاملة إلى البرنامج المسمى "CLMandelbrotMaster" مثل وسطاءٍ عبر سطر الأوامر. تستمع البرامج العاملة إلى طلبات الاتصال القادمة من البرنامج الرئيسي، ولهذا يجب أن يعرف البرنامج الرئيسي العناوين التي سيُرسِل إليها تلك الطلبات. على سبيل المثال، إذا كان البرنامج العامل مُشغَّلًا على ثلاثة حواسيب عناوينها هي: <code>172.21.7.101</code> و <code>172.21.7.102</code> و <code>172.21.7.103</code>، يُمكِننا أن نُشغِّل البرنامج "CLMandelbrotMaster" بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_42" style="">
<span class="pln">java  </span><span class="typ">CLMandelbrotMaster</span><span class="pln">  </span><span class="lit">172.21</span><span class="pun">.</span><span class="lit">7.101</span><span class="pln">  </span><span class="lit">172.21</span><span class="pun">.</span><span class="lit">7.102</span><span class="pln">  </span><span class="lit">172.21</span><span class="pun">.</span><span class="lit">7.103</span></pre>

<p>
	سينُشِئ البرنامج الرئيسي اتصالًا شبكيًا مع البرنامج العامل بكل عنوان بروتوكول إنترنت، وستُستخدَم تلك الاتصالات للتواصل بين كُلٍ من البرنامج الرئيسي والبرامج العاملة.
</p>

<p>
	يُمكِننا تشغيل عدة نسخٍ من البرنامج "CLMandelbrotWorker" على نفس الحاسوب، ولكن ينبغي أن يستمع كُلًا منها إلى الاتصالات الشبكية القادمة عبر أرقام منافذٍ مختلفة. كما يُمكِننا تشغيل البرنامج "CLMandelbrotWorker" على نفس الحاسوب الذي يَعمَل عليه البرنامج "CLMandelbrotMaster"، ولربما ستلاحظ عندها زيادة سرعة البرنامج إذا كان حاسوبك يحتوي على عدة معالجات. ألقِ نظرةً على التعليقات الموجودة بشيفرة البرنامج لمزيدٍ من المعلومات. نستعرض فيما يلي بعض الأوامر المُمكِن استخدامها لتشغيل البرنامج الرئيسي مع نسختين من البرنامج العامل على نفس الحاسوب. اُكتُبها بنوافذ سطر أوامر مختلفة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_44" style="">
<span class="pln">java  </span><span class="typ">CLMandelbrotWorker</span><span class="pln">                             </span><span class="pun">(</span><span class="typ">Listens</span><span class="pln"> on </span><span class="kwd">default</span><span class="pln"> port</span><span class="pun">)</span><span class="pln">

java  </span><span class="typ">CLMandelbrotWorker</span><span class="pln">  </span><span class="lit">2501</span><span class="pln">                       </span><span class="pun">(</span><span class="typ">Listens</span><span class="pln"> on port </span><span class="lit">2501</span><span class="pun">)</span><span class="pln">

java  </span><span class="typ">CLMandelbrotMaster</span><span class="pln">  localhost  localhost</span><span class="pun">:</span><span class="lit">2501</span></pre>

<p>
	يَحلّ البرنامج "CLMandelbrotMaster" نفس المشكلة بالضبط في كل مرةٍ نُشغِّل بها. في الواقع، طبيعة المشكلة ذاتها غير مهم، ولكنها تمثّل هنا حساب البيانات التي يتطلَّبها رسم جزءٍ صغير من صورة مجموعة ماندلبرو Mandelbrot Set الشهيرة. إذا كنت تريد رؤية الصورة الناتجة، ألغِ التعليق الموجود فوق الاستدعاء <code>saveImage()‎</code> بنهاية البرنامج <code>main()‎</code> المُعرَّف بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/CLMandelbrotMaster.java" rel="external nofollow">CLMandelbrotMaster.java</a>).
</p>

<p>
	يُمكِنك تشغيل البرنامج "CLMandelbrotMaster" مع عددٍ مختلف من البرامج العاملة لرؤية مدى اعتمادية الزمن الذي يتطلَّبه حل المشكلة على عدد البرامج العاملة. لاحِظ استمرار البرامج العاملة بالعمل حتى بعد انتهاء البرنامج الرئيسي؛ أي يُمكِنك تشغيل البرنامج الرئيسي عدة مرات دون الاضطرار لإعادة تشغيل البرامج العاملة في كل مرة. علاوةً على ذلك، إذا شغَّلت البرنامج "CLMandelbrotMaster" دون أن تُمرِّر إليه أي وسطاء، فإنه سيحلّ المشكلة بالكامل بمفرده، ويُمكِنك بذلك رؤية الزمن الذي يتطلَّبه حل المشكلة بدون الحوسبة المُوزَّعة.
</p>

<p>
	في الواقع، جرَّبنا ذلك بإحدى الحواسيب القديمة والبطيئة جدًا، ووجدنا أن البرنامج "CLMandelbrotMaster" قد استغرق 40 ثانية لحل المشكلة بمفرده؛ في حين استغرق 43 ثانية عند تشغيل برنامجٍ عاملٍ واحد. تُمثِّل تلك الزيادة العمل الإضافي المطلوب لاستخدام الشبكات، حيث يتطلَّب إنشاء الاتصال الشبكي، وإرسال الرسائل عبر الشبكة المزيد من الوقت. في المقابل، اِستغرَق البرنامج 22 ثانية فقط لحل المشكلة عند تشغيل برنامجين عاملين بحواسيبٍ مختلفة. في تلك الحالة، نفَّذ كل برنامجٍ منهما نصف العمل على التوازي، ولذلك أنُجزَت العملية بنصف الوقت تقريبًا.
</p>

<p>
	يستمر الزمن بالنقصان مع زيادة عدد البرامج العاملة، ولكن إلى حدٍ معين؛ لأن البرنامج الرئيسي لديه أيضًا بعض العمل الواجب إنجازه مهما ازداد عدد البرامج العاملة، وعليه لا يُمكِن للزمن الكلي المطلوب لحل المشكلة أن يكون أقل من الزمن الذي يستغرقه البرنامج الرئيسي لتنفيذ عمله. وفي تلك الحالة، بدا أن أقل زمنٍ ممكنٍ هو 5 ثوانٍ تقريبًا.
</p>

<p>
	والآن، لنرى طريقة برمجة هذا التطبيق المُوزَّع. سيُقسِّم البرنامج الرئيسي المشكلة إلى مجموعة مهام؛ بحيث تُمثَّل كل مهمة منها باستخدام كائنٍ ينتمي إلى الصنف <code>CLMandelbrotTask</code>. تُرسَل تلك المهام إلى البرامج العاملة، التي ينبغي لها أن تعيد نتائجها بعد أن تحسبها. يَعنِي ذلك أننا بحاجة إلى نوعٍ من البروتوكول للتواصل؛ ولذلك قررنا استخدام مجرى محارف character stream لهذا الغرض.
</p>

<p>
	سيُشفِّر البرنامج الرئيسي كل مهمةٍ بهيئة سطرٍ نصي يُرسَل إلى البرنامج العامل، الذي سيَفك التشفير ويُعيد السطر إلى كائنٍ من الصنف <code>CLMandelbrotTask</code> ليتمكَّن من فهم المهمة المفترض تنفيذها. بعد ذلك، يُنجِز البرنامج العامل المهمة، ثم يُشفِّر النتائج بهيئة سطرٍ نصي آخر يُرسَل عائدًا إلى البرنامج الرئيسي. أخيرًا، يَفُك البرنامج الرئيسي الشيفرة، ويَدمِج النتيجة مع نتائج المهام الأخرى. بعد اكتمال جميع المهام ودمج نتائجها معًا، تكون المشكلة قد حُلَّت.
</p>

<p>
	لا يستقبل كل برنامجٍ عاملٍ مهمةً واحدةً فقط، وإنما متتاليةً من المهام؛ فبمجرد أن يُنجز مهمةً معينة، ويُرسِل نتائجها، تُسنَد إليه مهمةٌ جديدة. يَستقبِل البرنامج العامل الأمر "close" بعد اكتمال جميع المهام ليخبره بأن عليه إغلاق الاتصال. يُجرَى كل ما سبق ضمن تابعٍ، اسمه <code>handleConnection()‎</code> بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/CLMandelbrotWorker.java" rel="external nofollow">CLMandelbrotWorker.java</a>، حيث يُستدعَى ذلك التابع لمعالجة اتصالٍ قد اُنشئ بالفعل مع البرنامج الرئيسي. يَستخدِم ذلك التابع بدوره تابعًا آخر، اسمه <code>readTask()‎</code> لفك تشفير المهمة التي استقبلها من البرنامج الرئيسي؛ كما يستخدِم التابع <code>writeResults()‎</code> بهدف تشفير نتائج المهمة قبل إرسالها إلى البرنامج الرئيسي. يجب عليه أيضًا معالجة أي أخطاءٍ ممكنة. انظر تعريف التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_46" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> handleConnection</span><span class="pun">(</span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">BufferedReader</span><span class="pln"> in </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedReader</span><span class="pun">(</span><span class="pln"> 
                </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">InputStreamReader</span><span class="pun">(</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">())</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
      </span><span class="typ">PrintWriter</span><span class="pln"> out </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">getOutputStream</span><span class="pun">());</span><span class="pln">
      </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">String</span><span class="pln"> line </span><span class="pun">=</span><span class="pln"> in</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">  </span><span class="com">// رسالة من البرنامج الرئيسي</span><span class="pln">
         </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">line </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// واجهنا نهاية المجرى. لا ينبغي أن يحدث ذلك</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"Connection closed unexpectedly."</span><span class="pun">);</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
         </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">line</span><span class="pun">.</span><span class="pln">startsWith</span><span class="pun">(</span><span class="pln">CLOSE_CONNECTION_COMMAND</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// يُمثِّل الانتهاء الطبيعي للاتصال</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Received close command."</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
         </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">line</span><span class="pun">.</span><span class="pln">startsWith</span><span class="pun">(</span><span class="pln">TASK_COMMAND</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// يُمثِل مهمةً من النوع‫ CLMandelbrotTask ينبغي أن ينفذها الخيط</span><span class="pln">
            </span><span class="typ">CLMandelbrotTask</span><span class="pln"> task </span><span class="pun">=</span><span class="pln"> readTask</span><span class="pun">(</span><span class="pln">line</span><span class="pun">);</span><span class="pln">  </span><span class="com">// فك تشفير الرسالة</span><span class="pln">
            task</span><span class="pun">.</span><span class="pln">compute</span><span class="pun">();</span><span class="pln">  </span><span class="com">// نفِّذ المهمة</span><span class="pln">
            out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">writeResults</span><span class="pun">(</span><span class="pln">task</span><span class="pun">));</span><span class="pln">  </span><span class="com">//  أرسِل النتائج</span><span class="pln">
            out</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">  </span><span class="com">// تأكّد من إرسال النتائج</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
         </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// ليس هناك أي رسائل أخرى ضمن البروتوكول</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"Illegal command received."</span><span class="pun">);</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Client connection closed with error "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   finally </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">  </span><span class="com">// تأكّد من إغلاق المقبس</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لا يُنفَّذ التابع المُعرَّف بالأعلى بخيطٍ thread مُنفصل، فالبرنامج العامل لديه شيءٌ واحدٌ فقط ليفعله بأي لحظة، ولا يحتاج إلى عدة خيوط.
</p>

<p>
	لنعود الآن إلى البرنامج الرئيسي <a href="http://math.hws.edu/javanotes/source/chapter12/CLMandelbrotMaster.java" rel="external nofollow">CLMandelbrotMaster.java</a>، حيث نواجه وضعًا أكثر تعقيدًا. يجب أن يُنشِئ البرنامج اتصالًا مع مجموعةٍ من البرامج العاملة عبر مجموعةٍ من الاتصالات الشبكية، وسيَستخدِم البرنامج لإنجاز ذلك عدة خيوط؛ بحيث يُعالِج كل خيطٍ منها الاتصال مع برنامج عاملٍ واحد. تُوضِّح الشيفرة الوهمية pseudocode التالية الفكرة العامة للبرنامج <code>main()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_48" style="">
<span class="com">// أنشِئ المهام التي ينبغي تنفيذها وضفها إلى الرتل</span><span class="pln">
create the tasks that must be performed and add them to a </span><span class="typ">queue</span><span class="pln">
</span><span class="com">// إذا لم تُمرَّر أي وسائط بالأمر</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> there are no command line arguments </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// يُنفِّذ البرنامج الرئيسي كل شيء بنفسه </span><span class="pln">
   </span><span class="typ">Remove</span><span class="pln"> each task from the </span><span class="typ">queue</span><span class="pln"> and perform it</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// تُنفِّذ البرامج العاملة المهام</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> each command line argument</span><span class="pun">:</span><span class="pln">
      </span><span class="com">// احصل على معلومات العامل من وسيط سطر الأوامر</span><span class="pln">
      </span><span class="typ">Get</span><span class="pln"> information about a worker from command line argument</span><span class="pun">.</span><span class="pln">
      </span><span class="com">// أنشِئ خيطًا جديدًا وشغِّله لكي يُرسِل المهام إلى البرامج العاملة</span><span class="pln">
      </span><span class="typ">Create</span><span class="pln"> and start a thread to send tasks to workers</span><span class="pun">.</span><span class="pln">
   </span><span class="com">// انتظر اكتمال جميع الخيوط</span><span class="pln">
   </span><span class="typ">Wait</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> all threads to terminate</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">// جميع المهام قد انتهت بفرض عدم حدوث أخطاء</span></pre>

<p>
	تُوضَع المهام بمتغيرٍ اسمه <code>tasks</code> من نوع الرتل <code>ConcurrentBlockingQueue&lt;CLMandelbrotTask&gt;‎</code>؛ وتَقَرأ خيوط الاتصال المهام من ذلك الرتل، وتُرسِلها إلى البرامج العاملة. يُستخدَم التابع <code>tasks.poll()‎</code> لقراءة مهمةٍ من الرتل؛ فإذا كان الرتل فارغًا، فسيُعيد القيمة <code>null</code>، والتي تُعدّ إشارةً إلى أن جميع المهام قد أُسنَدت بالفعل وأن بإمكان خيط الاتصال أن ينتهي.
</p>

<p>
	يتولى كل خيط اتصال مهمة إرسال متتاليةٍ من المهام إلى خيطٍ عاملٍ معين؛ واستقبال النتائج التي يعيدها ذلك الخيط العامل؛ كما أنه مسؤولٌ عن إنشاء اتصالٍ مع الخيط العامل بالبداية. تُوضِّح الشيفرة الوهمية pseudocode التالية الفكرة العامة للعملية التي يُنفِّذها خيط الاتصال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_50" style="">
<span class="com">// أنشِئ مقبسًا متصلًا مع البرنامج العامل</span><span class="pln">
</span><span class="typ">Create</span><span class="pln"> a socket connected to the worker program</span><span class="pun">.</span><span class="pln">
</span><span class="com">// أنشِئ مجرى دخل ومجرى خرج للتواصل مع البرنامج العامل</span><span class="pln">
</span><span class="typ">Create</span><span class="pln"> input and output streams </span><span class="kwd">for</span><span class="pln"> communicating with the worker</span><span class="pun">.</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">Let</span><span class="pln"> task </span><span class="pun">=</span><span class="pln"> tasks</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">().</span><span class="pln">
   </span><span class="typ">If</span><span class="pln"> task </span><span class="pun">==</span><span class="pln"> null
      </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">  </span><span class="com">// جميع المهام قد أسنَدت </span><span class="pln">
   </span><span class="com">// شفِّر‫ المهمة إلى رسالة وأرسلها إلى البرنامج العامل</span><span class="pln">
   </span><span class="typ">Encode</span><span class="pln"> the task into a message and transmit it to the worker</span><span class="pun">.</span><span class="pln">
   </span><span class="com">// اقرأ رد العامل</span><span class="pln">
   </span><span class="typ">Read</span><span class="pln"> the response from the worker</span><span class="pun">.</span><span class="pln">
   </span><span class="com">// فك تشفير الرد وعالجه</span><span class="pln">
   </span><span class="typ">Decode</span><span class="pln"> and process the response</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">// أرسِل الأمر‫ "close" إلى البرنامج العامل</span><span class="pln">
</span><span class="typ">Send</span><span class="pln"> a </span><span class="str">"close"</span><span class="pln"> command to the worker</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Close</span><span class="pln"> the socket</span><span class="pun">.</span></pre>

<p>
	سيَعمَل ذلك بطريقة مناسبة، ولكن هناك بعض الملاحظات: أولًا، يجب أن يكون الخيط مُهيأً للتعامل مع أخطاء الشبكة. على سبيل المثال، قد تُغلَق إحدى البرامج العاملة بصورةٍ غير متوقَّعة، وسيتمكَّن البرنامج الرئيسي من إكمال عمله في تلك الحالة نظرًا لوجود برامجٍ عاملةٍ أخرى. (عندما تُشغِّل البرنامج، يُمكِنك أن تُجرِّب ما يلي: أوقف إحدى البرامج العاملة بالضغط على "CONTROL-C"، وسترى أنه بإمكان البرنامج الرئيسي أن يكتمل بنجاح). تَنشَأ المشكلة إذا حدث خطأٌ بينما يَعمَل الخيط على مهمةٍ معينة؛ ونظرًا لأننا نهدف إلى حل المشكلة كاملةً، لا بُدّ إذًا من إعادة إسناد تلك المهمة إلى برنامجٍ عاملٍ آخر. يُمكِننا تحقيق ذلك بإعادة المهام غير المكتملة إلى رتل المهام.
</p>

<p>
	لسوء الحظ، لا يُعالج البرنامج الذي نَستعرِضه هنا جميع الأخطاء المحتملة. على سبيل المثال، إذا فَشَل خيط العامل الأخير، فلن يكون هناك عملاء آخرين نُسنِد إليهم المهام غير المكتملة. علاوةً على ذلك، إذا عُلّقَ الاتصال الشبكي دون أن يُولِّد خطأً فعليًا، سيُعلَّق البرنامج أيضًا نتيجة انتظاره لرد العامل. لا بُدّ لأي برنامج متين أن يجد طريقةً يَكشِف من خلالها عن تلك المشكلة ومن ثم يُعيد إسناد المهمة.
</p>

<p>
	ثانيًا، تترك الطريقة الموضحة بالأعلى البرنامج العامل دون أي عملٍ أثناء معالجة البرنامج الرئيسي لرد العامل. سيكون من الأفضل لو أُسندت مهمةٌ جديدةٌ إلى البرنامج العامل قبل معالجة ردّه على المهمة السابقة؛ حيث سيُبقِي ذلك البرنامج العامل مُنشغِلًا؛ كما سيَسمَح بتنفيذ العمليتين على التوازي بدلًا من تنفيذهما على التوالي. بهذا المثال، تستغرق معالجة الرد زمنًا قصيرًا للغاية، ولذلك قد لا يُمثِل ترك العامل منتظرًا أو تشغيله على الفور فارقًا كبيرًا، ولكن ينبغي عمومًا تطبيق التوازي بأقصى قدرٍ ممكن. ألقِ نظرةً على التصور العام للخوارزمية بعد التعديل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4591_52" style="">
<span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// أنشِئ مقبسًا متصلًا مع البرنامج العامل</span><span class="pln">
   </span><span class="typ">Create</span><span class="pln"> a socket connected to the worker program</span><span class="pun">.</span><span class="pln">
   </span><span class="com">// أنشِئ مجرى دخل ومجرى خرج للتواصل مع البرنامج العامل</span><span class="pln">
   </span><span class="typ">Create</span><span class="pln"> input and output streams </span><span class="kwd">for</span><span class="pln"> communicating with the worker</span><span class="pun">.</span><span class="pln">
   </span><span class="typ">Let</span><span class="pln"> currentTask </span><span class="pun">=</span><span class="pln"> tasks</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">().</span><span class="pln">
   </span><span class="com">// شفِّر‫ `currentTask` إلى رسالة وأرسلها إلى البرنامج العامل</span><span class="pln">
   </span><span class="typ">Encode</span><span class="pln"> currentTask into a message and send it to the worker</span><span class="pun">.</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="com">// اِقرأ الرد من البرنامج العامل</span><span class="pln">
      </span><span class="typ">Read</span><span class="pln"> the response from the worker</span><span class="pun">.</span><span class="pln">
      </span><span class="typ">Let</span><span class="pln"> nextTask </span><span class="pun">=</span><span class="pln"> tasks</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">().</span><span class="pln">
      </span><span class="typ">If</span><span class="pln"> nextTask </span><span class="pun">!=</span><span class="pln"> null </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// أرسل‫ `nextTask` إلى البرنامج العامل قبل معالجة الرد على المهمة `currentTask`</span><span class="pln">
         </span><span class="com">// شفِّر‫ `nextTask` إلى رسالة وأرسلها إلى البرنامج العامل</span><span class="pln">
         </span><span class="typ">Encode</span><span class="pln"> nextTask into a message and send it to the worker</span><span class="pun">.</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="com">// فك تشفير الرد على المهمة‫ `currentTask` وعالجه</span><span class="pln">
      </span><span class="typ">Decode</span><span class="pln"> and process the response to currentTask</span><span class="pun">.</span><span class="pln">
      currentTask </span><span class="pun">=</span><span class="pln"> nextTask</span><span class="pun">.</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">currentTask </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
         </span><span class="kwd">break</span><span class="pun">;</span><span class="pln"> </span><span class="com">// جميع المهام قد أسنَدت</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="com">// أرسِل الأمر‫ "close" إلى البرنامج العامل</span><span class="pln">
   </span><span class="typ">Send</span><span class="pln"> a </span><span class="str">"close"</span><span class="pln"> command to the worker</span><span class="pun">.</span><span class="pln">
   </span><span class="com">// أغلق المقبس</span><span class="pln">
   </span><span class="typ">Close</span><span class="pln"> the socket</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// أعِد المهمة غير المكتملة إن وجدت إلى رتل المهام مرة أخرى</span><span class="pln">
   </span><span class="typ">Put</span><span class="pln"> uncompleted task</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> back into the task </span><span class="typ">queue</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
finally </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// أغلق المقبس</span><span class="pln">
   </span><span class="typ">Close</span><span class="pln"> the connection</span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُمكِنك الإطلاع على الصنف المتداخل <code>WorkerConnection</code> المُعرَّف بالملف <a href="http://math.hws.edu/javanotes/source/chapter12/CLMandelbrotMaster.java" rel="external nofollow">CLMandelbrotMaster.java</a> لترى طريقة تحويل الشيفرة الوهمية السابقة إلى شيفرة جافا.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c12/s4.html" rel="external nofollow">Section 4: Threads and Networking</a> من فصل Chapter 12: Threads and Multiprocessing من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%B2%D9%8A-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1485/" rel="">الخيوط threads والمعالجة على التوازي في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/" rel="">البرمجة باستخدام الخيوط threads في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">مقدمة إلى الخيوط Threads في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel="">كيفية إنشاء عدة خيوط وفهم التزامن في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1486</guid><pubDate>Wed, 23 Mar 2022 09:46:18 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62E;&#x64A;&#x648;&#x637; threads &#x648;&#x627;&#x644;&#x645;&#x639;&#x627;&#x644;&#x62C;&#x629; &#x639;&#x644;&#x649; &#x627;&#x644;&#x62A;&#x648;&#x627;&#x632;&#x64A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%B2%D9%8A-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1485/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62109764965b2_---.png.afc0897a0ed7c656e1e60230f106853c.png" /></p>

<p>
	استخدمنا تقنية البرمجة على التوازي بمثال القسم الفرعي التعاود داخل الخيوط من المقال السابق <a href="http://xn--https-cdhh4euc/academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/" rel="external nofollow">البرمجة باستخدام الخيوط threads في جافا</a> لتنفيذ أجزاءٍ صغيرة من مهمةٍ كبيرة، حيث تَسمَح تلك التقنية للحواسيب مُتعدّدة المعالجات بإكمال عملية المعالجة بوتيرةٍ أسرع. ولكننا في الواقع لم نَستخدِم لا الطريقة الأفضل لتقسيم المشكلة ولا الطريقة الأفضل لإدارة الخيوط threads. سنتناول بهذا القسم نسختين من نفس البرنامج؛ حيث سنُحسِّن بالنسخة الأولى طريقة تقسيم المشكلة إلى عدّة مهام؛ بينما سنُحسِّن بالنسخة الثانية طريقة اِستخدام الخيوط. بالإضافة إلى ذلك، سنمرّ عبر مجموعةٍ من الأصناف المبنية مسبقًا والتي تُوفِّرها جافا لدعم المعالجة المتوازية، وسنناقش بنهاية القسم التابعين <code>wait()‎</code> و <code>notify()‎</code> المُستخدمين للتحكُّم بالعمليات المتوازية تحكمًا مباشرًا.
</p>

<h2>
	تقسيم المشكلات
</h2>

<p>
	يُقسِّم البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo1.java" rel="external nofollow">MultiprocessingDemo1.java</a> مُهمة حساب صورة إلى عدة مهامٍ فرعية، ويُسنِد كُلًا منها إلى خيط. يَعمَل هذا البرنامج بنجاح، ولكن هناك مشكلة متمثّلة في إمكانية استغراق بعض المهام وقتًا أطول من المهام الأخرى. في حين يُقسِّم البرنامج الصورة إلى أجزاءٍ متساوية، تتطلَّب بعض أجزائها معالجةً أكثر من أجزاءٍ أخرى. إذا شغَّلت البرنامج باستخدام ثلاثة خيوط، ستلاحظ أن معالجة الجزء الأوسط تستغرِق وقتًا أطول بعض الشيء من معالجة الجزأين السفلي والعلوي.
</p>

<p>
	في العموم، عندما نُقسِّم مشكلةً معينةً إلى عدة مشكلاتٍ فرعيةٍ أصغر، يكون من الصعب توقُّع الزمن الذي ستحتاجه معالجة كل مشكلةٍ منها. والآن، لنفترض أن لدينا مشكلةً فرعيةً معينة تستغرق زمنًا أطول من غيرها. في تلك الحالة، ستكتمل جميع الخيوط عدا الخيط المسؤول عن معالجة تلك المشكلة؛ حيث سيستمر بالعمل لفترةٍ طويلةٍ نسبيًا، وسيَعمَل معالجٌ واحدٌ فقط خلال تلك الفترة دون بقية المعالجات.
</p>

<p>
	إذا احتوى الحاسوب مثلًا على معالجين، وقسَّمنا مشكلةً ما إلى مشكلتين فرعيتين، ثم أنشأنا خيطًا لكل مشكلةٍ منهما. نطمح باستخدامنا لمعالجيّن إلى الحصول على الإجابة بنصف الزمن الذي سيستغرقه الحصول عليها عند استخدام معالجٍ واحد، ولكن إذا كان حل مشكلةٍ منهما يحتاج إلى أربعة أضعاف الزمن الذي يحتاجه حل المشكلة الأخرى، فسيكون معالجًا واحدًا فقط مُشغَّلًا غالبية الوقت، ولم نُقلِل في تلك الحالة الزمن المطلوب لحل المشكلة بنسبةٍ أكبر من 20%.
</p>

<p>
	حتى لو تمكَّنا من تقسيم المشكلة إلى عدة مشكلاتٍ فرعية تتطلَّب زمن المعالجة نفسه، فإننا لا نستطيع الاعتماد ببساطةٍ على حقيقة كونها ستتطلَّب زمنًا متساويًا؛ حيث من الممكن أن تكون بعض المعالجات منهمكةً بتشغيل برامجٍ أخرى، أو قد يكون بعضها أبطأ عمومًا (هذا ليس محتملًا إذا شغَّلنا البرنامج على حاسوبٍ واحد، ولكن تختلف سرعة المعالجات بالحوسبة المُوزَّعة من خلال مجموعة حواسيب عبر الشبكة -وهو ما سنفعله لاحقًا-، وتُعدّ عندها مسألةً مهمة).
</p>

<p>
	تتمثّل التقنية الأكثر شيوعًا للتعامل مع كل تلك المشكلات في تقسيم المشكلة إلى عددٍ كبيرٍ من المشكلات الفرعية الأصغر؛ أي أكبر بكثير من عدد المعالجات الموجودة، وسيضطّر بالتالي كل معالجٍ لحلّ عدة مشكلاتٍ فرعية. عندما يُكمِل معالج مهمةً فرعيةً معينة، تُسنَد إليه مهمةٌ فرعيةٌ أخرى ليَعمَل عليها إلى أن تُسنَد جميع المهام الفرعية. سيظل بالطبع هناك اختلافٌ بالزمن الذي تتطلَّبه كل مهمة، وبالتالي قد يُكمِل معالجٌ معينٌ عدة مشكلاتٍ فرعية، بينما قد يَعمَل معالجٌ آخر على مهمةٍ واحدةٍ معقدة؛ كما قد يُكمِل معالج بطيءٌ أو مشغولٌ مهمةً واحدةً فقط أو اثنتين، بينما قد يُنهِي معالجٌ آخر خمس أو ست مهام.
</p>

<p>
	في العموم، سيَعمَل كل معالجٍ وفقًا لسرعته الخاصة، وستستمر غالبية المعالجات بالعمل إلى قرب اكتمال المعالجة بالكامل بشرط أن تكون المشكلات الفرعية صغيرةً كفاية. ويُطلَق على ذلك "توزيع الحمل load balancing"؛ أي توزيع حمل المعالجة بين المعالجات المتاحة لإبقائها جميعًا منشغلةً بأقصى ما يُمكِن. ستنهي بعض المعالجات بالطبع عملها قبل بعضها الآخر، ولكن بفترةٍ لا تتعدى الزمن المطلوب لإتمام المهمة الفرعية الأطول.
</p>

<p>
	كما ذكرنا بالأعلى، ينبغي أن يكون حجم المشكلات الفرعية صغيرًا لا غاية في الصغر؛ لأن تقسيم المشكلة وإسنادها إلى المعالجات يتطلَّب حملًا إضافيًا overhead؛ فإذا كانت المشكلات الفرعية غايةً في الصغر، سيتراكم الحمل الإضافي ويُشكِّل فارقًا بمقدار العمل الكلي المطلوب. كانت المهمة بالمثال السابق هي حساب لون كل بكسلٍ بالصورة، فإذا أردنا تقسيم تلك المهمة إلى عدة مهامٍ فرعية، هناك عدة احتمالات؛ فقد تتكوَّن كل مهمة فرعية مثلًا من حساب لون بكسلٍ واحدٍ فقط، ولكنها تُعدّ صغيرة للغاية. بدلًا من ذلك، ستتكوَّن كل مهمةٍ فرعية من حساب لون صفٍ واحد من البكسلات. نظرًا لوجود عدة مئاتٍ من الصفوف بكل صورة، سيكون عدد المهام الفرعية كبيرًا كفاية، كما سيكون حجم كل مهمةٍ فرعيةٍ منها مناسبًا. سينتج عن ذلك توزيعًا جيدًا للحمل مع قدرٍ معقولٍ من الحمل الإضافي.
</p>

<p>
	ملاحظة: تُعدّ المشكلة التي ناقشناها بالمثال السابق سهلةً للغاية بالنسبة للبرمجة على التوازي؛ بمعنى أنه عند تقسيم مشكلة حساب ألوان الصورة إلى عدة مشكلاتٍ فرعية أصغر، ستَظِل جميع المشكلات الفرعية مستقلةً تمامًا، وبالتالي يُمكِن معالجة أي عددٍ منها بنفس الوقت ووفقًا لأي ترتيب. في المقابل، قد تتطلّب بعض المهام الفرعية بمشكلاتٍ أخرى النتائج المحسوبة بواسطة بعض المهام الأخرى؛ أي أن المهام الفرعية غير مستقلة، وبالتالي ستتعقد الأمور في تلك الحالات، وسيُصبح ترتيب تنفيذ المهام الفرعية مُهمًا. علاوةً على ذلك، سيكون من الضروري توفير طريقةٍ ما لتشارُك تلك النتائج بين المهام؛ وفي حالة تنفيذها بخيوطٍ مختلفة، سنضطّر إلى مواجهة كل تلك المشكلات المُتعلّقة بالتحكم بوصول الخيوط إلى الموارد التشاركية. لذلك، يُعدّ تقسيم مشكلةٍ معينةٍ لمعالجتها على التوازي أمرًا أصعب بكثير مما قد يوحي به المثال السابق، ولكن بالنهاية، يُعدّ ذلك موضوعًا لدورةٍ تدريبية عن الحوسبة المتوازية لا دورةٍ عن أساسيات البرمجة.
</p>

<h2>
	مجمع الخيوط وأرتال المهام
</h2>

<p>
	بعد أن نُقرِّر طريقة تقسيم المشكلة إلى عدة مهامٍ فرعية، ينبغي أن نعرف كيفية إسناد تلك المهام الفرعية إلى الخيوط؛ حيث ينبغي وفقًا للأسلوب كائني التوجه object-oriented تمثيل كل مهمةٍ فرعيةٍ بواسطة كائن، ولأن كل مهمة تُمثِّل عملية معالجة معينة، فمن البديهي أن يُعرِّف ذلك الكائن تابع نسخة instance method يُنفِّذ تلك العملية.
</p>

<p>
	من الضروري استدعاء التابع المقابل لمهمةٍ معينة حتى تُنفَّذ، وسيكون تابع المعالجة لهذا البرنامج، هو <code>run()‎</code>، وسيُنفِّذ الكائن المُمثِّل للمهمة <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1114/" rel="">الواجهة interface</a> القياسية <code>Runnable</code> التي ناقشناها بقسم إنشاء الخيوط وتشغيلها من مقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">مقدمة إلى الخيوط Threads في جافا</a>. تُعدّ تلك الواجهة الطريقة المباشرة لتمثيل مهام المعالجة، ويُمكِننا إنشاء خيطٍ جديدٍ لكل كائن <code>Runnable</code>، ولكن في حالة وجود عددٍ كبيرٍ من المهام، لا يكون لذلك أي معنى؛ نتيجةً لمقدار الحمل الإضافي الناتج عن إنشاء كل خيطٍ جديد. بدلًا من ذلك، يُفضَّل إنشاء عددٍ قليل من الخيوط، بحيث يُسمَح لكُلٍ منها بتنفيذ قدرٍ معيّنٍ من المهام.
</p>

<p>
	لاحِظ أن عدد الخيوط التي ينبغي استخدامها غير معروف، وقد يعتمد على المشكلة التي نحاول حلها. يتمحور الهدف عمومًا في إبقاء جميع معالجات الحاسوب مُشغَّلة؛ فبالنسبة لمثال حساب الصورة، كان إنشاء خيطٍ مقابل كل معالجٍ مناسبًا، ولكنه قد لا يتناسب مع جميع المشكلات. وبالتحديد، إذا كان هناك خيطٌ قد يتسبَّب بحدوث تعطيلٍ block لفترةٍ طويلة بينما ينتظر وقوع حدثٍ event معين، أو بينما ينتظر الوصول إلى موردٍ ما، فلربما يكون من الأفضل إنشاء خيوطٍ إضافية؛ لكي يَعمَل عليها كل معالجٍ أثناء تعطُّل الخيوط الأخرى.
</p>

<p>
	يُطلَق على مجموعة الخيوط المُتاحة لتنفيذ المهام اسم "مجمع الخيوط thread pool"، وتُستخدَم بغرض تجنُّب إنشاء خيطٍ جديدٍ لكل مهمة، حيث تُسنَد المهمة المطلوب تنفيذها إلى أي خيطٍ مُتاحٍ بالمجمع.
</p>

<p>
	عندما تنشغِل جميع الخيوط الموجودة بالمجمع، تضطّر أي مهامٍ أخرى إضافية للانتظار إلى أن تُتاح إحدى الخيوط، ويُعدّ ذلك تطبيقًا مباشرًا على <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3-stack-%D9%88%D8%A7%D9%84%D8%B1%D8%AA%D9%84-queue-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF%D8%A9-adt-r1396/" rel="">الرتل queue</a>؛ حيث يرتبط بمجمع الخيوط رتلٌ مُكوَّنٌ من المهام قيد الانتظار. بمجرد توفُّر مهمةٍ جديدة، ستُضَاف إلى الرتل؛ وبمجرد انتهاء خيطٍ معينٍ من تنفيذ المهمة المُوكَلة إليه، فسيَحصُل على مهمةٍ أخرى من الرتل ليَعمَل عليها.
</p>

<p>
	هناك رتل مهامٍ task queue وحيدٍ لمجمع الخيوط. يَعنِي ذلك استخدام جميع خيوط المجمع نفس الرتل، فهو يُعدّ موردًا تشاركيًا. كما هو الحال مع أي موردٍ تشاركي، قد تقع حالات التسابق race conditions، ولهذا يكون استخدام المزامنة synchronization ضروريًا؛ فقد يحاول بدونها خيطان قراءة عنصرٍ من الرتل بنفس الوقت مثلًا، وعندها سيَحصُل كلاهما على نفس العنصر. حاول التعرف على الأماكن المُحتمَلة لوقوع حالات التسابق بالتابع <code>dequeue()‎</code> المُعرَّف في قسم الأرتال Queues من الفصل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3-stack-%D9%88%D8%A7%D9%84%D8%B1%D8%AA%D9%84-queue-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF%D8%A9-adt-r1396/" rel="">المكدس Stack والرتل Queue وأنواع البيانات المجردة ADT</a>.
</p>

<p>
	تُوفِّر جافا الصنف <code>ConcurrentLinkedQueue</code> من أجل حل تلك المشكلة؛ وهو صنفٌ مُعرَّفٌ بحزمة <code>package java.util.concurrent</code> إلى جانب أصنافٍ أخرى مفيدة للبرمجة على التوازي. لاحِظ أنه صنف ذو معاملاتٍ غير مُحدَّدة النوع parameterized، ولذلك إذا أردنا إنشاء رتلٍ لحَمْل كائناتٍ من النوع <code>Runnable</code>، يُمكِننا كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_12" style="">
<span class="typ">ConcurrentLinkedQueue</span><span class="pun">&lt;</span><span class="typ">Runnable</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">queue</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ConcurrentLinkedQueue</span><span class="pun">&lt;&gt;();</span></pre>

<p>
	يُمثِّل هذا الصنف رتلًا مُنفَّذًا مثل قائمةٍ مترابطة linked list، كما أن عملياته <a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel="">متزامنةً</a> بطريقةٍ مناسبة. ليست العمليات المُعرَّفة بالصنف <code>ConcurrentLinkedQueue</code> نفس العمليات على الأرتال التي عهدناها؛ فعلى سبيل المثال، يُضيف التابع <code>queue.add(x)‎</code> العنصر الجديد <code>x</code> إلى نهاية <code>queue</code>؛ بينما يَحذِف التابع <code>queue.poll()‎</code> عنصرًا من مقدمة الرتل queue. إذا كان الرتل فارغًا، يعيد التابع <code>queue.poll()‎</code> القيمة <code>null</code>، ولهذا يُمكِننا استخدامه لفحص فيما إذا كان الرتل فارغًا، أو لإسترجاع عنصرٍ إذا لم يكن كذلك.
</p>

<p>
	في الحقيقة، يُفضَّل فعل ذلك على هذا النحو؛ فقد يؤدي التأكُّد مما إذا كان الرتل فارغًا قبل قراءة عنصرٍ منه إلى وقوع حالة تسابق؛ حيث يستطيع خيطٌ آخر بدون تحقيق المزامنة حذف آخر عنصرٍ بالرتل باللحظة الواقعة بين لحظتي اختبارٍ للرتل فيما إذا كان فارغًا ومحاولة قراءة العنصر من الرتل، وفي تلك الحالة لن نجد شيئًا عند محاولة قراءة العنصر. في المقابل، يُعدّ التابع <code>queue.poll()‎</code> بمثابة عمليةٍ ذرية atomic.
</p>

<p>
	يُمكِننا استخدام رتلٍ ينتمي إلى الصنف <code>ConcurrentLinkedQueue</code> مع مجمع خيوطٍ لحساب الصورة من المثال السابق، حيث سنُنشِئ جميع المهام المسؤولة عن حساب الصورة ونضيفها إلى الرتل، ثم سنُنشِئ الخيوط التي ستُنفِّذ تلك المهام، ونُشغِّلها. سيتضمَّن كل خيطٍ منها <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%AA%D9%84-blocks-%D9%88%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A7%D8%AA-loops-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D8%B9%D8%A7%D8%AA-branches-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1062/" rel="">حلقة تكرار loop</a> بحيث يُستدعى تابع الرتل <code>poll()‎</code> بكل تكرارٍ لقراءة مهمةٍ منه ثم تُنفَّذ. نظراً لأن المهمة هي كائنٌ من النوع <code>Runnable</code>، فكل ما ينبغي أن يفعله الخيط هو استدعاء تابع المهمة <code>run()‎</code>؛ عندما يُعيد التابع <code>poll()‎</code> القيمة <code>null</code>، فإن الرتل قد أصبح فارغًا أي أن جميع المهام قد أُسندَت لخيوطٍ أخرى، ويُمكِن عندها للخيط المُستدعِي الانتهاء.
</p>

<p>
	يُنفِّذ البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo2.java" rel="external nofollow">MultiprocessingDemo2.java</a> تلك الفكرة؛ حيث يَستخدِم رتلًا، اسمه <code>taskQueue</code> من النوع <code>ConcurrentLinkedQueue&lt;Runnable&gt;‎</code> لحَمْل المهام. كما يَسمَح البرنامج للمُستخدِم بإلغاء العملية قبل انتهائها، حيث يَستخدِم متغيرًا منطقيًا متطايرًا volatile، اسمه <code>running</code> إشارةً للخيط بأن المُستخدِم قد ألغى العملية. عندما تُصبِح قيمة ذلك المتغير مساويةً للقيمة <code>false</code>، ينبغي أن ينتهي الخيط حتى لو لم يَكن الرتل فارغًا. تُعرِّف الشيفرة التالية <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D8%AA%D8%AF%D8%A7%D8%AE%D9%84%D8%A9-nested-classes-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1115/" rel="">الصنف المُتداخِل</a> <code>WorkerThread</code> لتمثيل الخيوط:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_14" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">WorkerThread</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">Runnable</span><span class="pln"> task </span><span class="pun">=</span><span class="pln"> taskQueue</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">();</span><span class="pln"> </span><span class="com">// اقرأ مهمةً من الرتل</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">task </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
                    </span><span class="kwd">break</span><span class="pun">;</span><span class="pln"> </span><span class="com">// لأن الرتل فارغ</span><span class="pln">
                task</span><span class="pun">.</span><span class="pln">run</span><span class="pun">();</span><span class="pln">  </span><span class="com">// Execute the task;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        finally </span><span class="pun">{</span><span class="pln">
            threadFinished</span><span class="pun">();</span><span class="pln"> </span><span class="com">// ‫تذكّر أن الخيط قد انتهى.</span><span class="pln">
   </span><span class="com">// ‫أضفناها بعبارة `finally` لنتأكَّد من استدعائها</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَستخدِم البرنامج الصنف المُتداخِل <code>MandelbrotTask</code> لتمثيل مهمة حساب صفٍ واحدٍ من البكسلات، حيث يُنفِّذ ذلك الصنف الواجهة <code>Runnable</code>، ويَحسِب تابعه <code>run()‎</code> لون كل بكسلٍ بالصف، ثم يَنسَخ تلك الألوان إلى الصورة. تُوضِح الشيفرة التالية ما يفعله البرنامج عند بدء عملية المعالجة مع حذف قليلٍ من التفاصيل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_16" style="">
<span class="pln">taskQueue </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ConcurrentLinkedQueue</span><span class="pun">&lt;</span><span class="typ">Runnable</span><span class="pun">&gt;();</span><span class="pln"> </span><span class="com">// Create the queue.</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> row </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> row </span><span class="pun">&lt;</span><span class="pln"> height</span><span class="pun">;</span><span class="pln"> row</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// عدد الصفوف الموجودة بالصورة</span><span class="pln">
    </span><span class="typ">MandelbrotTask</span><span class="pln"> task</span><span class="pun">;</span><span class="pln">
    task </span><span class="pun">=</span><span class="pln"> </span><span class="pun">...</span><span class="pln"> </span><span class="pun">;</span><span class="pln">  </span><span class="com">// أنشِئ مهمةً لمعالجة صفٍ واحد من الصورة</span><span class="pln">
    taskQueue</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">task</span><span class="pun">);</span><span class="pln"> </span><span class="com">// أضف المهمة إلى الرتل</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> threadCount </span><span class="pun">=</span><span class="pln"> </span><span class="pun">...</span><span class="pln"> </span><span class="pun">;</span><span class="pln"> </span><span class="com">// عدد الخيوط الموجودة بالمجمع. يضبُطه المُستخدِم</span><span class="pln">
workers </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WorkerThread</span><span class="pun">[</span><span class="pln">threadCount</span><span class="pun">];</span><span class="pln">
running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اضبط الإشارة قبل بدء تشغيل الخيوط</span><span class="pln">
threadsRemaining </span><span class="pun">=</span><span class="pln"> workers</span><span class="pun">;</span><span class="pln">  </span><span class="com">// عدد الخيوط قيد التشغيل</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> threadCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WorkerThread</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">setPriority</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">currentThread</span><span class="pun">().</span><span class="pln">getPriority</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تجدر الإشارة هنا إلى أنه من الضروري إضافة المهام إلى الرتل قبل بدء تشغيل الخيوط؛ لأننا نَستخدِم الرتل الفارغ بمثابة إشارةٍ إلى ضرورة انتهاء الخيوط؛ أي إذا وجدت الخيوط عند تشغيلها الرتل فارغًا، فستنتهي فورًا دون أن تُنجِز أي مهمة.
</p>

<p>
	جرِّب البرنامج "MultiprocessingDemo2"؛ فهو يَحسِب نفس الصورة التي يَحسبِها البرنامج "MultiprocessingDemo1"، ولكنه يختلف عنه بالترتيب الذي تُحسَب على أساسه الصفوف في حالة استخدام أكثر من خيط. إذا شاهدت البرنامج بحرص، فستلاحظ عدم إضافة صفوف البكسلات بالترتيب من أعلى إلى أسفل؛ لأن خيط الصف <code>i+1</code> قد يُنهِي عمله قبل أن يُنهِي خيط الصف <code>i</code> عمله أو حتى ما يَسبقه من صفوف. ستلاحِظ هذا التأثير بقدرٍ أكبر إذا استخدمت عدد خيوطٍ أكبر مما يحتويه حاسوبك من معالجات. جرِّب 20 خيطًأ مثلًا.
</p>

<h2>
	نمط المنتج والمستهلك والأرتال المعطلة
</h2>

<p>
	يُنشِئ البرنامج "MultiprocessingDemo2" مجمع خيوطٍ جديد تمامًا بكل مرةٍ يَرسِم بها صورة، وهو ما يبدو سيئًا. أليس من المفترض إنشاء مجموعةٍ واحدةٍ فقط من الخيوط ببداية البرنامج، واستخدامها لحساب أي صورة؟ حيث أن الهدف من اِستخدَام مجمع الخيوط بالنهاية هو انتظار الخيوط للمهام الجديدة وتنفيذها. ولكننا، لم نُوضِّح حتى الآن أي طريقةٍ لجعل خيطٍ ينتظر قدوم مهمةٍ جديدة، حيث يُوفِّر <strong>الرتل المُعطِّل blocking queue</strong> ذلك.
</p>

<p>
	يُعدّ الرتل المُعطِّل تنفيذًا لإحدى أنماط المعالجة على التوازي، وهو <strong>نمط المُنتِج والمُستهلِك producer/consumer</strong>؛ حيث يُستخدَم هذا النمط في حالة وجود "مُنتِجٍ" واحدٍ أو أكثر لشيءٍ معين إلى جانب وجود "مُستهلِكٍ" واحد أو أكثر لذلك الشيء. لا بُدّ أن يَعمَل جميع المُنتِجين والمُستهِلِكين بنفس الوقت (أي معالجة على التوازي). إذا لم يَتوفَّر أي شيء للمعالجة، فسيضطّر المُستهلِك للانتظار إلى أن تتَوفَّر إحداها. قد يضطَّر المُنتِج ببعض التطبيقات للانتظار أحيانًا: إذا كان مُعدّل استهلاك الأشياء واحدًا لكل دقيقةٍ مثلًا، فمن غير المنطقي أن يكون معدّل إنتاجها اثنين لكل دقيقة مثلًا؛ لأنه سيؤدي إلى تراكمها بصورةٍ غير محدودة. ولذلك، لا بُدّ من تخصيص حدٍ أقصى لعدد الأشياء قيد الانتظار، وإذا وصلنا إلى ذلك الحد، لا بُدّ أن يتوقَّف المنتجون عن إنتاج أي أشياءٍ أخرى لبعض الوقت.
</p>

<p>
	والآن، سنحتاج إلى طريقةٍ لنقل الأشياء من المُنتجِين إلى المُستهلِكين، حيث يُعدّ الرتل الحل الأمثل لذلك: سيَضَع المنتجون الأشياء بأحد طرفي الرتل، وسيقرأها المُستهلِكون من الطرف الآخر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="92285" href="https://academy.hsoub.com/uploads/monthly_2022_02/001Producer_Consumer.png.2b1687181b3d178b7bb9eb455c4cdc97.png" rel=""><img alt="001Producer_Consumer.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92285" data-unique="mspjerpk0" src="https://academy.hsoub.com/uploads/monthly_2022_02/001Producer_Consumer.png.2b1687181b3d178b7bb9eb455c4cdc97.png"></a>
</p>

<p>
	نظرًا لأننا نُجرِي معالجةً على التوازي، سنَستخدِم رتلًا متزامنًا، ولكننا نحتاج إلى ما هو أكثر من ذلك؛ فعندما يُصبِح الرتل فارغًا، نريد طريقةً تضطّر المستهلِكين للانتظار إلى أن يُوضَع شيءٌ جديدٌ بالرتل؛ وإذا أصبح ممتلئًا، نريد طريقةً تضطّر المُنتجِين للانتظار إلى أن يُتاح مكانٌ بالرتل. يُمثَّل كُلٌ من المُستهلِكين والمُنتجِين باستخدام الخيوط. إذا كان الخيط مُتوقِّفًا بانتظار حدوث شيءٍ معين، يُقَال أنه مُعطَّل blocked، ولذلك يُعدُّ الرتل المُعطِّل هو نوع الرتل الذي نحتاجه.
</p>

<p>
	عندما نَستخدِم رتلًا معطِّلًا، وكان ذلك الرتل فارغًا، ستؤدي عملية سحب dequeue عنصرٍ من الرتل إلى تعطيل المُستدعِي؛ أي إذا حاول خيطٌ معينٌ سحَب عنصرٍ من رتلٍ فارغ، فسيتوقَّف إلى أن يُتاح عنصرٌ جديد، وسيستيقظ عندها الخيط ويقرأ العنصر ويُكمِل عمله. بالمثل، إذا وصل الرتل إلى سعته القصوى، وحاول مُنتِجٌ معينٌ إدخال عنصرٍ به، فسيتعطَّل إلى أن يُتَاح مكانٌ بالرتل.
</p>

<p>
	تحتوي حزمة <code>java.util.concurrent</code> على صنفين يُنفِّذان الرتل المُعطِّل: <code>LinkedBlockingQueue</code> و <code>ArrayBlockingQueue</code>، وهما من الأنواع ذات المعاملات غير مُحدَّدة النوع parameterized types؛ أي يَسمحَا بتخصيص نوع العنصر الذي سيَحمله الرتل، ويُنفِّذ كلٌ منهما الواجهة <code>BlockingQueue</code>. إذا كان <code>bqueue</code> رتلًا مُعطِّلًا ينتمي إلى أحد الصنفين السابقين، فإنه يُعرِّف العمليات التالية:
</p>

<ul>
<li>
		<code>bqueue.take()‎</code>: يَحذِف <code>item</code> من الرتل ويعيده؛ فإذا كان الرتل فارغًا عند استدعائه، يتعطَّل الخيط المُستدعِي إلى أن يُتاح عنصرٌ جديدٌ بالرتل. يُبلِّغ التابع عن <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%AA-exceptions-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1068/" rel="">استثناءٍ exception</a> من النوع <code>InterruptedException</code> إذا قُوطَع الخيط أثناء تعطُّله.
	</li>
	<li>
		<code>bqueue.put(item)‎</code>: يُضيِف <code>item</code> إلى الرتل. إذا كان للرتل سعةً قصوى وقد أصبح ممتلئًا، يتعطَّل الخيط المُستدعِي إلى أن يُتَاح مكانٌ بالرتل. يُبلِّغ التابع عن استثناءٍ من النوع <code>InterruptedException</code> إذا قُوطَع الخيط أثناء تعطُّله.
	</li>
	<li>
		<code>bqueue.add(item)‎</code>: يُضيف <code>item</code> إلى الرتل في حالة وجود مكانٍ متاح. إذا كان للرتل سعةً قصوى وقد أصبح ممتلئًا، يُبلِّغ التابع عن استثناءٍ من النوع <code>IllegalStateException</code>، ولكنه لا يُعطِّل المُستدعِي.
	</li>
	<li>
		<code>bqueue.clear()‎</code>: يحذِف جميع العناصر من الرتل ويُهملها.
	</li>
</ul>
<p>
	تُعرِّف الأرتال المُعطِّلة بجافا الكثير من التوابع الأخرى. يتشابه التابع <code>bqueue.poll(500)‎</code> مثلًا مع التابع <code>bqueue.take()‎</code> باستثناء أنه يُعطِّل لمدة 500 ميللي ثانية بحدٍ أقصى، ولكن التوابع المذكورة بالأعلى كافيةٌ للأمثلة التالية. لاحِظ وجود تابعين لإضافة العناصر إلى الرتل؛ حيث يُعطِّل التابع <code>bqueue.put(item)‎</code> المُستدعِي إذا لم يَكُن هناك أي مكانٍ آخر متاحٍ بالرتل، ولذلك يُستخدَم مع أرتال التعطيل محدودة السعة؛ بينما لا يُعطِّل التابع <code>bqueue.add(item)‎</code> المُستدعِي، ولذلك يُستخدَم مع أرتال التعطيل التي تملُك سعةً غير محدودة.
</p>

<p>
	تُخصَّص السعة القصوى لرتلٍ من النوع <code>ArrayBlockingQueue</code> عند إنشائه. تُنشِئ الشيفرة التالية على سبيل المثال رتلًا مُعطِّلًا بإمكانه حَمْل ما يَصِل إلى 25 كائنٍ من النوع <code>ItemType</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_18" style="">
<span class="typ">ArrayBlockingQueue</span><span class="pun">&lt;</span><span class="typ">ItemType</span><span class="pun">&gt;</span><span class="pln"> bqueue </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayBlockingQueue</span><span class="pun">&lt;&gt;(</span><span class="lit">25</span><span class="pun">);</span></pre>

<p>
	إذا اِستخدَمنا الرتل المُعرَّف بالأعلى، فسيُعطِّل التابع <code>bqueue.put(item)‎</code> المُستدعِي إذا كان <code>bqueue</code> يحتوي على 25 عنصرٍ بالفعل؛ بينما يُبلِّغ التابع <code>bqueue.add(item)‎</code> عن استثناءٍ في تلك الحالة. يضمَن ذلك عدم إنتاج العناصر بمعدّلٍ أسرع من مُعدّل استهلاكها. في المقابل، يُستخدَم الصنف <code>LinkedBlockingQueue</code> لإنشاء أرتالٍ مُعطِّلة بسعةٍ غير محدودة. ألقِ نظرةً على المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_20" style="">
<span class="typ">LinkedBlockingQueue</span><span class="pun">&lt;</span><span class="typ">ItemType</span><span class="pun">&gt;</span><span class="pln"> bqueue </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinkedBlockingQueue</span><span class="pun">&lt;&gt;();</span></pre>

<p>
	تُنشِئ الشيفرة السابقة رتلًا بدون حدٍ أقصى لعدد العناصر التي يُمكِنه حملها. في تلك الحالة، لن يتسبَّب التابع <code>bqueue.put(item)‎</code> بحدوث تعطيل نهائيًا، ولن يُبلِّغ التابع <code>bqueue.add(item)‎</code> عن استثناءٍ من النوع <code>IllegalStateException</code> على الإطلاق. يُمكِننا اِستخدَام الصنف <code>LinkedBlockingQueue</code> إذا أردنا تجنُّب تعطيل المُنتجِين، ولكن من الجهة الأخرى، لا بُدّ أن نتأكَّد من بقاء الرتل بحجمٍ معقول. يؤدي التابع <code>bqueue.take()‎</code> إلى حدوث تعطيلٍ إذا كان الرتل فارغًا لكلا الصنفين.
</p>

<p>
	يَستخدِم البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo3.java" rel="external nofollow">MultiprocessingDemo3.java</a> رتلًا من الصنف <code>LinkedBlockingQueue</code> بدلًا من الصنف <code>ConcurrentLinkedQueue</code> المُستخدَم في النسخة السابقة من البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo2.java" rel="external nofollow">MultiprocessingDemo2.java</a>. يحتوي الرتل في هذا المثال على عدة مهام (أي العناصر المنتمية للنوع <code>Runnable</code>)، ويُصرَّح عنه على أنه تابع نسخة instance variable اسمه <code>taskQueue</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_22" style="">
<span class="typ">LinkedBlockingQueue</span><span class="pun">&lt;</span><span class="typ">Runnable</span><span class="pun">&gt;</span><span class="pln"> taskQueue</span><span class="pun">;</span></pre>

<p>
	عندما ينقر المُستخدِم على زر "Start" لحساب الصورة، نُضيف جميع مهام حساب الصورة إلى ذلك الرتل باستدعاء التابع <code>taskQueue.add(task)‎</code> لكل مهمةٍ على حدى. من المهم أن يحدث ذلك دون تعطيل؛ لأننا نُنشِئ تلك المهام بخيط معالجة الأحداث events الذي لا ينبغي تعطيله. لن ينمو الرتل إلى ما لانهاية؛ لأن البرنامج يَعمَل على صورةٍ واحدةٍ فقط بكل مرة، وهناك مئاتٌ قليلةٌ من المهام للصورة الواحدة.
</p>

<p>
	على نحوٍ مشابه للنسخة السابقة من البرنامج، ستحذِف الخيوط العاملة المنتمية إلى مجمع الخيوط thread pool المهام من الرتل وتُنفِّذها، ولكنها -أي الخيوط- تُنشَئ مرةً واحدةً فقط ببداية البرنامج؛ أو بتعبيرٍ أدق عندما ينقر المُستخدِم على زر "Start" لأول مرة. يُعاد استخدام نفس تلك الخيوط لأي عددٍ من الصور، وفي حالة عدم وجود أي مهامٍ أخرى، سيُصبِح الرتل فارغًا، وستتعطَّل الخيوط إلى حين قدوم مهامٍ جديدة. تُنفِّذ تلك الخيوط حلقة تكرارٍ لا نهائية infinite loop، وتُعالِج المهام للأبد، ولكنها تقضِي وقتًا طويلًا معطَّلةً بانتظار إضافة مهمةٍ جديدةٍ إلى الرتل. انظر تعريف الصنف المُمثِل لتلك الخيوط:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_24" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">WorkerThread</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">WorkerThread</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            setPriority</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">currentThread</span><span class="pun">().</span><span class="pln">getPriority</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            setDaemon</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        start</span><span class="pun">();</span><span class="pln"> </span><span class="com">// يبدأ الخيط العمل بمجرد تشغيله</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">Runnable</span><span class="pln"> task </span><span class="pun">=</span><span class="pln"> taskQueue</span><span class="pun">.</span><span class="pln">take</span><span class="pun">();</span><span class="pln"> </span><span class="com">// انتظر مهمة إذا كان ذلك ضروريًا</span><span class="pln">
                task</span><span class="pun">.</span><span class="pln">run</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	[1] يُعرِّف هذا الصنف الخيوط العاملة الموجودة بمجمع الخيوط، حيث يَعمَل كائنٌ من هذا الصنف بحلقةٍ يَسترجِع كل <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1-for-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1065/" rel="">تكرارٍ</a> منها مهمةً من الرتل <code>taskQueue</code> ثم يستدعي التابع <code>run()‎</code> الخاص بتلك المهمة. إذا كان الرتل فارغًا، يتعطَّل الخيط إلى أن تتوفَّر مهمةٌ جديدةٌ بالرتل. يتولى الباني مهمة بدء الخيط، وبالتالي لن يضطر البرنامج <code>main</code> لفعل ذلك. يَعمَل الخيط بأولويةٍ أقل من أولوية الخيط الذي اِستدعَى الباني. صُمِّم الصنف لكي يَعمَل بحلقةٍ لا نهائية تنتهي فقط عند إغلاق <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">آلة جافا الافتراضية Java virtual machine</a>، وهذا على فرض عدم تبليغ المهام المُنفَّذة عن أي استثناءاتٍ وهو افتراضٌ صحيحٌ بهذا البرنامج. يَضبُط الباني الخيط ليعمل مثل خيطٍ خفي، وبالتالي تنتهي آلة جافا الافتراضية تلقائيًا عندما تكون الخيوط الوحيدة الموجودة من النوع الخفي، أي لا يَمنَع وجود تلك الخيوط آلة جافا من الإغلاق.
</p>

<p>
	ينبغي فحص طريقة عمل مجمع الخيوط، حيث تُنشَأ الخيوط وتُشغَّل قبل وجود أي مهمة. يَستدعِي كل خيطٍ منها التابع <code>taskQueue.take()‎</code> فورًا، ونظرًا لأن رتل المهام فارغ، تتعطَّل جميع الخيوط بمجرد تشغيلها. والآن، لكي نُعالِج صورةً معينة، يُنشِئ خيط معالجة الأحداث المهام الخاصة بتلك الصورة، ويُضيفها إلى الرتل. بمجرد حدوث ذلك، تعود الخيوط للعمل وتبدأ بمعالجة المهام، ويستمر الحال كذلك إلى أن يَفرُغ الرتل مرةً أخرى. في حالة تشغيل البرنامج بحاسوبٍ مُتعدّد المعالجات، تبدأ بعض الخيوط بمعالجة المهام المُضافة إلى الرتل بينما ما يزال خيط معالجة الأحداث مستمرٌ بإضافة المهام. عندما يُصبِح الرتل فارغًا، تتعطَّل الخيوط مجددًا إلى أن نرغب بمعالجة صورةٍ جديدة.
</p>

<p>
	إضافةً إلى ما سبق، قد نرغب بإلغاء معالجة صورةٍ معينةٍ قبل انتهائها، ولكننا لا نريد إنهاء الخيوط العاملة في تلك الحالة. عندما ينقر المُستخدِم على الزر "Abort"، يَستدعِي البرنامج التابع <code>taskQueue.clear()‎</code>، مما يَمنَع إسناد أي مهامٍ أخرى إلى الخيوط، ومع ذلك فمن المحتمل أن تكون بعض المهام قيد التنفيذ بالفعل بينما نُفرِّغ الرتل، وستكتمل بالتالي تلك المهام بعد إلغاء المعالجة المُفترَض كونهم أجزاءٌ منها، ولكننا لا نريد تطبيق خَرْج تلك المهام على الصورة.
</p>

<p>
	يُمكِننا حلّ تلك المشكلة بإسناد رقم وظيفة لكل وظيفة معالجة؛ حيث سيُخزَّن رقم الوظيفة الحالية بمتغير نسخة instance variable اسمه <code>jobNum</code>. ينبغي أن يحتوي كل كائن مُمثِّل لمهمة على تابع نسخة يُحدِّد الوظيفة التي تُعدّ تلك المهمة جزءًا منها. تزداد قيمة <code>jobNum</code> بمقدار الواحد عند انتهاء وظيفة؛ إما لأنها انتهت على نحوٍ طبيعي؛ أو لأن المُستخدِم قد ألغاها. عند اكتمال مهمةٍ معينة، لا بُدّ أن نوازن بين رقم الوظيفة المُخزَّن بكائن المهمة وبين <code>jobNum</code>؛ فإذا كانا متساويين، تكون المهمة جزءًا من الوظيفة الحالية، ويُطبَق خَرْجها على الصورة؛ أما إذا لم يكونا متساويين، تكون تلك المهمة جزءًا من الوظيفة السابقة، ويُهمَل خَرْجها.
</p>

<p>
	من المهم أن يكون الوصول إلى <code>jobNum</code> متزامنًا synchronized، وإلا قد يَفحَص خيطٌ معينٌ رقم الوظيفة بينما يزيده خيطٌ آخر، وعندها قد نَعرِض خرجًا معنيًّا لوظيفةٍ سابقة مع أننا ألغينا تلك الوظيفة. جميع التوابع التي تقرأ قيمة <code>jobNum</code> أو تُعدِّله في هذا البرنامج متزامنة. يُمكِنك قراءة <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo3.java" rel="external nofollow">شيفرة البرنامج</a> لترى طريقة عملها.
</p>

<p>
	هناك ملاحظةٌ إضافية عن البرنامج "MultiprocessingDemo3"، وهي: نحن لم نُوفِّر أي طريقةٍ لإنهاء الخيوط العاملة ضمن ذلك البرنامج أي أنها ستستمر بالعمل إلى أن نُغلِق آلة جافا الافتراضية Java Virtual Machine. يُمكِننا السماح بإنهاء الخيوط قبل ذلك باستخدام متغيرٍ متطايرٍ volatile، اسمه <code>running</code>، وضبط قيمته إلى <code>false</code> عندما نرغب بإنهائها، وسنُعرِّف التابع <code>run()‎</code> الموجود بالخيوط على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_27" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> running </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="typ">Runnable</span><span class="pln"> task </span><span class="pun">=</span><span class="pln"> taskQueue</span><span class="pun">.</span><span class="pln">take</span><span class="pun">();</span><span class="pln">
          task</span><span class="pun">.</span><span class="pln">run</span><span class="pun">();</span><span class="pln">
       </span><span class="pun">}</span><span class="pln">
       </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ومع ذلك، إذا كان هناك خيطٌ مُعطّلٌ نتيجةً لاستدعاء <code>taskQueue.take()‎</code>، فلن يتمكَّن من رؤية القيمة الجديدة للمتغيّر <code>running</code> قبل أن يعود للعمل. لنتأكَّد من إنهائه، يُمكِننا استدعاء التابع <code>worker.interrupt()‎</code> لكل خيط <code>worker</code> بعد ضبط قيمة <code>running</code> إلى <code>false</code>.
</p>

<p>
	في حالة تنفيذ خيطٍ لمهمةٍ بينما نضبُط قيمة <code>running</code> إلى <code>false</code>، فإنه لن ينتهي حتى يُكمِل تلك المهمة. إذا كانت المهام قصيرةً نسبيًا، لن يُشكِّل ذلك مشكلة، ولكن إذا استغرقت المهام وقتًا أطول مما ترغب بانتظاره، فلا بُدّ أن تَفحَص المهام قيمة <code>running</code> دوريًا، وتنتهي إذا أصبحت قيمته مساويةً القيمة <code>false</code>.
</p>

<h2>
	نهج ExecutorService لتنفيذ المهام
</h2>

<p>
	يشيع استخدام مجمعات الخيوط thread pools بالبرمجة على التوازي، ولذلك، تُوفِّر <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">جافا</a> أدوات عالية المستوى لإنشاء مجمعات الخيوط وإدارتها. تُعرِّف الواجهة <code>ExecutorService</code> من حزمة <code>java.util.concurrent</code> خدماتٍ يُمكِنها تنفيذ المهام المُرسَلة إليها. يحتوي الصنف <code>Executors</code> على توابعٍ ساكنة static تُنشِئ أنواعًا مختلفةً من النوع <code>ExecutorService</code>. وبالأخص، يُنشِئ التابع <code>Executors.newFixedThreadPool(n)‎</code> مجمع خيوطٍ مُكوَّن من عدد <code>n</code> من الخيوط، حيث <code>n</code> هي عدد صحيح. تُنشِئ الشيفرة التالية مجمع خيوط مُكوّنٍ من خيطٍ واحدٍ لكل معالج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_29" style="">
<span class="typ">int</span><span class="pln"> processors </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Runtime</span><span class="pun">.</span><span class="pln">getRuntime</span><span class="pun">().</span><span class="pln">availableProcessors</span><span class="pun">();</span><span class="pln">
</span><span class="typ">ExecutorService</span><span class="pln"> executor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Executors</span><span class="pun">.</span><span class="pln">newFixedThreadPool</span><span class="pun">(</span><span class="pln">processors</span><span class="pun">);</span></pre>

<p>
	يُستخدَم التابع <code>executor.execute(task)‎</code> لإرسال كائنٍ من النوع <code>Runnable</code> لتنفيذه، ويعود على الفور بعد وضعه للمهمة داخل رتل المهام المُنتظِرَة. تَحذِف الخيوط الموجودة بمجمع الخيوط المهام من الرتل وتُنفِّذها.
</p>

<p>
	يُخبِر التابع <code>executor.shutdown()‎</code> مجمع الخيوط بأن عليه الانتهاء بعد تنفيذ جميع المهام المُنتظِرَة، ويعود التابع على الفور دون أن ينتظر انتهاء الخيوط. بعد استدعاء ذلك التابع، لا يُسمَح بإضافة مهامٍ جديدة. يُمكِنك استدعاء <code>shutdown()‎</code> أكثر من مرة، ولن يُعدّ ذلك خطأً. لا تُعدّ الخيوط الموجودة بمجمع الخيوط خيوطًا خفية daemon threads؛ أي في حالة انتهاء الخيوط الأخرى دون إغلاق الخدمة، يكون وجود تلك الخيوط كافٍ لمنع إغلاق آلة جافا الافتراضية.
</p>

<p>
	يتشابه التابع <code>executor.shutdownNow()‎</code> مع التابع <code>executor.shutdown()‎</code>، إلا أنه يُهمِل المهام التي ما تزال قيد الانتظار بالرتل، وتُكمِل الخيوط المهام التي كانت قد حُذفت من الرتل بالفعل قبل الإغلاق.
</p>

<p>
	يَختلف البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo4.java" rel="external nofollow">MultiprocessingDemo4.java</a> عن البرنامج MultiprocessingDemo3؛ حيث يَستخدِم النوع <code>ExecutorService</code> بدلًا من الاستخدام المباشر للخيوط والأرتال المُعطِّلة. نظرًا لعدم وجود طريقةٍ بسيطةٍ تَسمَح للنوع <code>ExecutorService</code> بتجاهُل المهام المُنتظِرَة دون أن يُغلَق، يُنشِئ البرنامج "MultiprocessingDemo4" كائنًا جديدًا من النوع <code>ExecutorService</code> لكل صورة.
</p>

<p>
	يُمكِننا تمثيل المهام المُستخدَمة مع النوع <code>ExecutorService</code> بكائناتٍ من النوع <code>Callable&lt;T&gt;‎</code>، حيث يُمثِّل ذلك النوع واجهة نوع دالة functional interface ذات معاملاتٍ غير مُحدَّدة النوع، ويُعرِّف التابع <code>call()‎</code>، الذي لا يستقبل أي معاملاتٍ ويعيد النوع <code>T</code>. يُمثِل النوع <code>Callable</code> مهمةً تُخرِج قيمة.
</p>

<p>
	يُمكِننا إرسال كائنٍ <code>c</code> من النوع <code>Callable</code> إلى النوع <code>ExecutorService</code> باستدعاء التابع <code>executor.submit(c)‎</code>، حيث تُنفَّذ المهمة من النوع <code>Callable</code> بلحظةٍ ما في المستقبل. في تلك الحالة، كيف سنَحصُل على نتيجة المعالجة عند اكتمالها؟ يُمكِننا حل تلك المشكلة باستخدام واجهةٍ أخرى هي <code>Future&lt;T&gt;‎</code>، التي تُمثِّل قيمةً من النوع <code>T</code> قد تكون غير متاحةٍ حتى وقتٍ ما بالمستقبل. يعيد التابع <code>executor.submit(c)‎</code> قيمةً من النوع <code>Future</code> تُمثِّل نتيجة المعالجة المؤجَّلة.
</p>

<p>
	يُعرِّف كائنٌ <code>v</code> من النوع <code>Future</code> مجموعةً من التوابع، مثل الدالة المنطقية <code>v.isDone()‎</code> التي يُمكِننا استدعاؤها لفحص فيما إذا كانت نتيجة المعالجة قد أصبحت متاحة؛ وكذلك التابع <code>v.get()‎</code> الذي يسترجع نتيجة المعالجة المؤجَّلة، وسيُعطًّل إلى أن تُصبِح القيمة متاحة، كما قد يُبلِّغ عن استثناءات، ولذلك ينبغي استدعاؤه ضمن تعليمة <code>try..catch</code>.
</p>

<p>
	يَستخدِم المثال <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest4.java" rel="external nofollow">ThreadTest4.java</a> الأنواع <code>Callable</code> و <code>Future</code> و <code>ExecutorService</code> لعدّ عدد الأعداد الأولية الواقعة ضمن نطاقٍ معينٍ من الأعداد الصحيحة؛ كما يُجرِي نفس المعالجة التي أجراها البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest2.java" rel="external nofollow">ThreadTest2.java</a> في قسم الإقصاء التشاركي Mutual Exclusion وتعليمة التزامن synchronized من مقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">مقدمة إلى الخيوط Threads في جافا</a>. ستَعُدّ كل مهمةٍ فرعية في هذا البرنامج عدد الأعداد الأولية ضمن نطاقٍ أصغر من الأعداد الصحيحة، وستُمثَّل تلك المهام الفرعية من خلال كائناتٍ من النوع <code>Callable&lt;Integer&gt;‎</code> المُعرَّفة بالصنف المتداخل nested التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_31" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CountPrimesTask</span><span class="pln"> implements </span><span class="typ">Callable</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> min</span><span class="pun">,</span><span class="pln"> max</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">CountPrimesTask</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> min</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> max</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">min </span><span class="pun">=</span><span class="pln"> min</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">max </span><span class="pun">=</span><span class="pln"> max</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Integer</span><span class="pln"> call</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> count </span><span class="pun">=</span><span class="pln"> countPrimes</span><span class="pun">(</span><span class="pln">min</span><span class="pun">,</span><span class="pln">max</span><span class="pun">);</span><span class="pln">  </span><span class="com">// يبدأ بالعدّ</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> count</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	[1] تعدّ الكائنات المنتمية إلى هذا الصنف الأعداد الأولية الموجودة ضمن نطاقٍ معين من الأعداد الصحيحة من <code>min</code> إلى <code>max</code>. تُمرَّر قيمة المتغيرين <code>min</code> و <code>max</code> مثل معاملاتٍ للباني. يحسب التابع <code>call()‎</code> عدد الأعداد الأولية ثم يعيدها.
</p>

<p>
	ستُرسَل جميع المهام الفرعية إلى مجمع خيوط مُنفَّذ باستخدام النوع <code>ExecutorService</code>، وتُخزَّن النتائج من النوع <code>Future</code> التي يعيدها داخل <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-arrays-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1069/" rel="">مصفوفةٍ</a> من النوع <code>ArrayList</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_33" style="">
<span class="typ">int</span><span class="pln"> processors </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Runtime</span><span class="pun">.</span><span class="pln">getRuntime</span><span class="pun">().</span><span class="pln">availableProcessors</span><span class="pun">();</span><span class="pln">
</span><span class="typ">ExecutorService</span><span class="pln"> executor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Executors</span><span class="pun">.</span><span class="pln">newFixedThreadPool</span><span class="pun">(</span><span class="pln">processors</span><span class="pun">);</span><span class="pln">

</span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Future</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;&gt;</span><span class="pln"> results </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> numberOfTasks</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="typ">CountPrimesTask</span><span class="pln"> oneTask </span><span class="pun">=</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">;</span><span class="pln">
    </span><span class="typ">Future</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> oneResult </span><span class="pun">=</span><span class="pln"> executor</span><span class="pun">.</span><span class="pln">submit</span><span class="pun">(</span><span class="pln"> oneTask </span><span class="pun">);</span><span class="pln">
    results</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">oneResult</span><span class="pun">);</span><span class="pln">  </span><span class="com">// خزِّن الكائن الذي يُمثِّل النتيجة المؤجَلة</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	لا بُدّ أن نُضيف الأعداد الصحيحة الناتجة عن المهام الفرعية إلى المجموع النهائي. سنحصل أولًا على خَرْج تلك المهام باستدعاء التابع <code>get()‎</code> لعناصر <a href="https://academy.hsoub.com/programming/android/%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%8C-%D8%B7%D8%B1%D9%82-%D8%A7%D9%84%D8%AA%D8%AD%D9%88%D9%8A%D9%84-%D8%A8%D9%8A%D9%86-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA%D8%8C-%D9%88%D9%84%D9%85%D8%AD%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%A7%D9%84%D9%88%D8%B1%D8%A7%D8%AB%D8%A9-r334/" rel="">المصفوفة</a> المنتمية إلى النوع <code>Future</code>. لن تكتمل العملية إلا بعد انتهاء جميع المهام الفرعية، لأن التابع يُعطِّل المُستدعِي إلى أن تتوفَّر النتيجة. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_35" style="">
<span class="typ">int</span><span class="pln"> total </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">Future</span><span class="pun">&lt;</span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> res </span><span class="pun">:</span><span class="pln"> results</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        total </span><span class="pun">+=</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">get</span><span class="pun">();</span><span class="pln">  </span><span class="com">// انتظر اكتمال المهمة</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> 
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// لا ينبغي أن تحدث بهذا البرنامج</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	تابعا الانتظار Wait والتنبيه Notify
</h2>

<p>
	إذا كنا نريد كتابة تنفيذٍ للرتل المعطِّل، فينبغي أن نُعطِّل الخيط إلى حين وقوع حدثٍ معين؛ أي أن ينتظر الخيط وقوع ذلك الحدث، وينبغي أن نُبلِّغه عند وقوعه بطريقةٍ ما. سنَستخدِم لذلك خيطين؛ حيث يقع الفعل المُسبِّب للحدث المنتظَر (مثل إضافة عنصرٍ إلى رتل) بخيطٍ غير الخيط المُعطَّل.
</p>

<p>
	لا يُمثِّل ما يَلي مشكلةً للأرتال المُعطِّلة فقط؛ ففي حالة وجود خيطٍ يُنتِج خرجًا يحتاج إليه خيطٌ آخر، فإن ذلك يَفرِض نوعًا من التقييد على الترتيب الذي ينبغي للخيوط أن تُنفِّذ العمليات على أساسه. إذا وصلنا إلى النقطة التي يحتاج خلالها الخيط الثاني إلى الخرج الناتج عن الخيط الأول، قد يضطّر الخيط الثاني إلى التوقُّف وانتظار إتاحة ذلك الخرج؛ ونظرًا لأنه لا يستطيع الاستمرار، فإنه قد ينام sleep، ولا بُدّ في تلك الحالة من توفير طريقةٍ لتنبيهه عندما يُصبِح الخرج متاحًا، حتى يستيقظ ويُكمِل عملية المعالجة.
</p>

<p>
	تُوفِّر جافا بالطبع طريقةً لتنفيذ هذا النوع من الانتظار والتنبيه؛ حيث يحتوي الصنف <code>Object</code> على تابعي النسخة <code>wait()‎</code> و <code>notify()‎</code>، ويُمكِن استخدامهما مع أي كائن، كما يمكن للأرتال المعطِّلة اِستخدَام تلك التوابع ضمن تنفيذها الداخلي، ولكنها منخفضة المستوى وعرضةً للأخطاء؛ ولذلك يُفضَّل اِستخدام أساليب التحكُّم عالية المستوى مثل أرتال الأولوية قدر الإمكان. مع ذلك، من الجيد معرفة القليل عن التابعين <code>wait()‎</code> و <code>notify()‎</code>، فلربما قد تحتاج إلى استخدامهما مباشرةً. من غير المعروف فيما إذا كانت أصناف جافا القياسية للأرتال المُعطِّلة تَستخدِم هذين التابعين فعليًا، خاصةً مع توقُّر طرائقٍ أخرى لحل مشكلة الانتظار والتنبيه.
</p>

<p>
	السبب وراء ضرورة ربط التابعين <code>wait()‎</code> و <code>notify()‎</code> بالكائنات واضح، وبالتالي ليس هناك داعٍ للقلق بشأن ذلك، فهو يَسمَح على الأقل بتوجيه تنبيهاتٍ من أنواعٍ مختلفة إلى مستقبلين من أنواعٍ مختلفة اعتمادًا على تابع الكائن<code>notify()‎</code> المُستدعى.
</p>

<p>
	عندما يَستدعِي خيطٌ ما التابع <code>wait()‎</code> الخاص بكائنٍ معين، يتوقَّف ذلك الخيط وينام إلى حين استدعاء التابع <code>notify()‎</code> الخاص بنفس الكائن، حيث سيكون استدعاؤه ضروريًا من خلال خيطٍ آخر؛ لأن الخيط الذي اِستدعَى <code>wait()‎</code> سيكون نائمًا. تَعمَل إحدى الأنماط الشائعة على النحو التالي: يستدعِي خيط A التابع <code>wait()‎</code> عندما يحتاج إلى الخرج الناتج من خيط B، ولكن ذلك الخرج غير متاحٍ بعد. عندما يُحصِّل الخيط B الخرج المطلوب، فإنه يَستدعِي التابع <code>notify()‎</code> الذي سيوقِظ الخيط A إذا كان منتظرًا ليتمكَّن من اِستخدَام الناتج. في الواقع، ليس من الخطأ استدعاء التابع <code>notify()‎</code> حتى لو لم يَكُن هناك أي خيوطٍ مُنتظِرَة، فليس لها أي تأثير. لنُنفِّذ ذلك، ينبغي أن يُنفِّذ الخيط A شيفرةً مشابهةً لما يلي، حيث <code>obj</code> هو كائن:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_37" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> resultIsAvailable</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">)</span><span class="pln">
   obj</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">();</span><span class="pln">  </span><span class="com">// انتظر تنبيهًا بأن النتيجة مُتاحة</span><span class="pln">
useTheResult</span><span class="pun">();</span></pre>

<p>
	بينما ينبغي أن يُنفِّذ الخيط B شيفرةً مشابهةً لما يَلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_39" style="">
<span class="pln">generateTheResult</span><span class="pun">();</span><span class="pln">
obj</span><span class="pun">.</span><span class="pln">notify</span><span class="pun">();</span><span class="pln">  </span><span class="com">// أرسل تنبيهًا بأن النتيجة قد أصبحت متاحة</span></pre>

<p>
	تعاني تلك الشيفرة من حالة تسابق race condition، فقد يُنفِّذ الخيطان شيفرتهما بالترتيب التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_41" style="">
<span class="com">// يفحص الخيط‫ A التابع `resultIsAvailable()‎` ولا يجد النتيجة بعد، لذلك، يُنفِّذ تعليمة `obj.wait()‎`، ولكن قبل أن يفعل،</span><span class="pln">
</span><span class="lit">1.</span><span class="pln">  </span><span class="typ">Thread</span><span class="pln"> A checks resultIsAvailable</span><span class="pun">()</span><span class="pln"> and finds that the result is not ready</span><span class="pun">,</span><span class="pln">
        so it decides to execute the obj</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">()</span><span class="pln"> statement</span><span class="pun">,</span><span class="pln"> but before it does</span><span class="pun">,</span><span class="pln">
</span><span class="com">// ‫ينتهي الخيط B من عمله ويَستدعِي التابع `obj.notify()‎`</span><span class="pln">
</span><span class="lit">2.</span><span class="pln">  </span><span class="typ">Thread</span><span class="pln"> B finishes generating the result and calls obj</span><span class="pun">.</span><span class="pln">notify</span><span class="pun">()</span><span class="pln">
</span><span class="com">// ‫يَستدعِي الخيط A التابع `obj.wait()‎` لينتظر تنبيهًا بتوفُّر النتيجة </span><span class="pln">
</span><span class="lit">3.</span><span class="pln">  </span><span class="typ">Thread</span><span class="pln"> A calls obj</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">()</span><span class="pln"> to wait </span><span class="kwd">for</span><span class="pln"> notification that the result is ready</span><span class="pun">.</span></pre>

<p>
	ينتظر الخيط A بالخطوة الثالثة تنبيهًا لن يحدث أبدًا؛ لأن <code>notify()‎</code> قد اُستدعيَت بالفعل بالخطوة الثانية. يُمثِّل ذلك نوعًا من القفل الميت deadlock الذي يُمكِنه أن يترك الخيط A مُنتظِرًا للأبد. نحتاج إذًا إلى نوعٍ من المزامنة synchronization. يكمن حل تلك المشكلة في وضع شيفرة الخيطين A و B داخل تعليمة <code>synchronized</code>، ومن البديهي أن تكون المزامنة بناءً على نفس الكائن <code>obj</code> المُستخدَم عند استدعاء <code>wait()‎</code> و <code>notify()‎</code>.
</p>

<p>
	نظرًا لأهمية استخدام المزامنة عند كل استدعاءٍ للتابعين <code>wait()‎</code> و <code>notify()‎</code> تقريبًا، جعلته جافا أمرًا ضروريًا؛ أي بإمكان خيطٍ معينٍ استدعاء <code>obj.wait()‎</code> أو <code>obj.notify()‎</code> فقط إذا كان ذلك الخيط قد حَصَل على قفل المزامنة المُرتبِط بالكائن <code>obj</code>؛ أما إذا لم يَكُن قد حَصَل عليه، يحدث استثناء من النوع <code>IllegalMonitorStateException</code>. لا يتطلَّب هذا الاستثناء معالجةً إجباريةً ولا يُلتقَط على الأرجح. علاوةً على ذلك، قد يُبلِّغ التابع <code>wait()‎</code> عن اتستثناءٍ من النوع <code>InterruptedException</code>، ولذلك لا بُدّ من استدعائه ضمن تعليمة <code>try</code> لمعالجته.
</p>

<p>
	لنفحص الآن طريقة وصول خيطٍ معينٍ إلى نتيجةٍ يحسبها خيطٌ آخر. يُعدّ ذلك مثالًا مبسطًا على مشكلة المُنتِج والمُستهلِك producer/consumer، حيث يُنتَج عنصرٌ واحدٌ فقط ثم يُستهلَك. لنفترض أن لدينا متغيرًا تشاركيًا، اسمه <code>sharedResult</code> مُستخدَمٌ لنقل النتيجة من المُنتِج إلى المُستهلِك. عندما تُصبِح النتيجة جاهزة، يضبُط المُنتِج ذلك المتغير إلى قيمةٍ غير فارغة. يُحدِّد المُستهلِك من الجهة الأخرى فيما إذا كانت النتيجة جاهزةً أم لا بفحص قيمة المتغير <code>sharedResult</code> إذا كانت فارغة. سنَستخدِم مُتغيّرًا اسمه <code>lock</code> للمزامنة. يُمكِننا كتابة شيفرة الخيط المُمثِّل للمُنتِج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_43" style="">
<span class="pln">makeResult </span><span class="pun">=</span><span class="pln"> generateTheResult</span><span class="pun">();</span><span class="pln">  </span><span class="com">// غير متزامن</span><span class="pln">
synchronized</span><span class="pun">(</span><span class="pln">lock</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   sharedResult </span><span class="pun">=</span><span class="pln"> makeResult</span><span class="pun">;</span><span class="pln">
   lock</span><span class="pun">.</span><span class="pln">notify</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بينما سيُنفِّذ المُستهلِك الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_45" style="">
<span class="pln">synchronized</span><span class="pun">(</span><span class="pln">lock</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> sharedResult </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         lock</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">();</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   useResult </span><span class="pun">=</span><span class="pln"> sharedResult</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
useTheResult</span><span class="pun">(</span><span class="pln">useResult</span><span class="pun">);</span><span class="pln">  </span><span class="com">// Not synchronized!</span></pre>

<p>
	لاحِظ أن استدعاء كُلٍ من التابعين <code>generateTheResult()‎</code> و <code>useTheResult()‎</code> غير متزامن، لنَسمَح بتنفيذهما على التوازي مع الخيوط الأخرى التي قد تُجرِي تزامنًا بناءً على <code>lock</code>، ولكن نظرًا لأن المتغير <code>sharedResult</code> تشاركي، كان من الضروري أن تكون جميع مراجِعه references متزامنة؛ أي لا بُدّ من كتابتها داخل تعليمة <code>synchronized</code>، مع محاولة تنفيذ أقل ما يُمكِن عمومًا داخل كتل الشيفرة المتزامنة.
</p>

<p>
	ربما لاحظت شيئًا مضحكًا بالشيفرة: لا ينتهي <code>lock.wait()‎</code> قبل تنفيذ <code>lock.notify()‎</code>، ولكن نظرًا لأن كليهما مكتوبٌ داخل تعليمة <code>synchronized</code> بتزامنٍ مبني على الكائن نفسه، قد تتساءل: أليس من المستحيل تنفيذ هذين التابعين بنفس الوقت؟ في الواقع، يُعدّ التابع <code>lock.wait()‎</code> حالةً خاصة؛ فعندما يَستدعِي خيطٌ ما التابع <code>lock.wait()‎</code>، فإنه يترك قفله بالضرورة على كائن المزامنة، مما يَسمَح لخيطٍ آخرٍ بتنفيذ كتلة شيفرة داخل تعليمة <code>synchronized(lock)‎</code> أخرى يوجد بداخلها استدعاءٌ للتابع <code>lock.notify()‎</code>. وبالتالي، بعدما يُنهِي الخيط الثاني تنفيذ تلك الكتلة، يعود القفل إلى الخيط الأول المُستهلِك مما يُمكِّنه من إكمال عمله.
</p>

<p>
	تُنتَج في نمط المُنتِج والمُستهلِك العادي عدة نتائجٍ بواسطة خيط مُنتِجٍ واحدٍ أو أكثر، وتُستهلَك بواسطة خيط مُستهلِكٍ واحدٍ أو أكثر، وبدلًا من وجود كائن <code>sharedResult</code> وحيد، تجد قائمةً بالكائنات المُنتَجَة التي لم تُستهلَك بعد. لنفحص طريقة فعل ذلك باستخدام صنفٍ بسيطٍ للغاية يُنفِّذ العمليات الثلاثة على رتلٍ من النوع <code>LinkedBlockingQueue&lt;Runnable&gt;‎</code>، الذي اِستخدَمناه بالبرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo3.java" rel="external nofollow">MultiprocessingDemo3</a>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_47" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">LinkedList</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyLinkedBlockingQueue</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">LinkedList</span><span class="pun">&lt;</span><span class="typ">Runnable</span><span class="pun">&gt;</span><span class="pln"> taskList </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinkedList</span><span class="pun">&lt;</span><span class="typ">Runnable</span><span class="pun">&gt;();</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> clear</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        synchronized</span><span class="pun">(</span><span class="pln">taskList</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            taskList</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> add</span><span class="pun">(</span><span class="typ">Runnable</span><span class="pln"> task</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        synchronized</span><span class="pun">(</span><span class="pln">taskList</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            taskList</span><span class="pun">.</span><span class="pln">addLast</span><span class="pun">(</span><span class="pln">task</span><span class="pun">);</span><span class="pln">
            taskList</span><span class="pun">.</span><span class="pln">notify</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Runnable</span><span class="pln"> take</span><span class="pun">()</span><span class="pln"> throws </span><span class="typ">InterruptedException</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        synchronized</span><span class="pun">(</span><span class="pln">taskList</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">taskList</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">())</span><span class="pln">
                taskList</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> taskList</span><span class="pun">.</span><span class="pln">removeFirst</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	سنَستخدِم كائنًا من ذلك الصنف بديلًا عن الكائن <code>taskQueue</code> بالبرنامج "MultiprocessingDemo3".
</p>

<p>
	فضَّلنا إجراء المزامنة بناءً على الكائن <code>taskList</code>، ولكن كان من الممكن إجراؤها بناءً على أي كائنٍ آخر. يُمكِننا في الحقيقة استخدام توابعٍ متزامنة synchronized methods، وهو ما سيُكافِئ المزامنة بناءً على <code>this</code>.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: قد تَجِد استدعاءً للتابع <code>wait()‎</code> أو <code>notify()‎</code> داخل تابع نسخة متزامن بدون مرجع للكائن المُستخدَم. تذكَّر أن اِستخدَام <code>wait()‎</code> أو <code>notify()‎</code> ضمن هذا السياق يُكافِئ تمامًا اِستخدَام <code>this.wait()‎</code> أو <code>this.notify()‎</code>.
		</p>
	</div>
</blockquote>

<p>
	من الضروري أن يكون استدعاء التابع <code>taskList.clear()‎</code> مبنيًا على نفس الكائن حتى لو لم نَستدعِي <code>wait()‎</code> أو <code>notify()‎</code>؛ وإذا لم نَفعَل ذلك، قد تحدث حالة تسابق race condition، ألا وهي: قد تُفرَّغ القائمة بعدما يتأكَّد التابع <code>take()‎</code> من أن القائمة <code>taskList</code> غير فارغة وقبل أن يحاول حذف عنصرٍ منها. في تلك الحالة، ستُصبِح القائمة فارغة عند لحظة استدعاء <code>taskList.removeFirst()‎</code> مما سيتسبَّب بحدوث خطأ.
</p>

<p>
	في حالة تواجد عدة خيوطٍ متزامنة بناءً على كائن <code>obj</code> ومُنتظِرةٍ للتنبيه. يُوقِظ التابع <code>obj.notify()‎</code> عند استدعائه واحدًا فقط من تلك الخيوط المُنتظِرة؛ وإذا أردت أن توقظها جميعًا، ينبغي أن تَستدعِي التابع <code>obj.notifyAll()‎</code>. من المناسب اِستخدَام التابع <code>obj.notify()‎</code> بالمثال السابق؛ لأن الخيوط المُستهلِكة فقط هي الخيوط المُعطَّلة، ونحن نريد إيقاظ مُستهلِكٍ واحدٍ فقط عند إضافة مهمةٍ إلى الرتل، ولا يُهِم أي مُستهلِكٍ تُسنَد إليه المهمة.
</p>

<p>
	في المقابل، إذا كان لدينا رتلٌ مُعطِّل blocking queue بسعةٍ قصوى، أي أنه قد يُعطِّل المُنتجِين أو المُستهلِكِين؛ فعند إضافة مهمةٍ إلى الرتل، ينبغي التأكُّد من تنبيه خيط مُستهلِكٍ لا خيط مُنتِج، ويُمثِّل استدعاء التابع <code>notifyAll()‎</code> بدلًا من التابع <code>notify()‎</code> إحدى حلول تلك المشكلة، لأنه سيُنبِّه جميع الخيوط بما في ذلك أي خيط مُستهلِكٍ مُنتظِر.
</p>

<p>
	قد يعطيك اسم التابع <code>obj.notify()‎</code> انطباعًا خاطئًا. لا يُنبِّه ذلك التابع الكائن <code>obj</code> بأي شيء، وإنما يُنبِّه الخيط الذي اِستدعَى التابع <code>obj.wait()‎</code> إذا كان موجودًا. بالمثل، لا ينتظر الكائن <code>obj</code> بالاستدعاء <code>obj.wait()‎</code> أي شيء، وإنما الخيط المُستدعِي هو من ينتظر.
</p>

<p>
	وفي ملاحظة أخيرة بخصوص <code>wait</code>: هناك نسخةٌ أخرى من التابع <code>wait()‎</code>، وهي تَستقبِل زمنًا بوحدة الميللي ثانية مثل مُعامِل؛ وهنا سينتظر الخيط المُستدعِي للتابع <code>obj.wait(milliseconds)‎</code> تنبيهًا لفترةٍ تَصِل إلى القيمة الُممرَّرة بحدٍ أقصى؛ وإذا لم يحدث التنبيه خلال تلك الفترة، يستيقظ الخيط ويُكمِل عمله دون تنبيه. تُستخدَم تلك الخاصية عمليًا لتَسمَح لخيطٍ مُنتظِر بالاستيقاظ كل فترة لإنجاز مهمةٍ دوريةٍ معينة، مثل التسبُّب في ظهور رسالة مثل "Waiting for computation to finish".
</p>

<p>
	لنفحص الآن مثالًا يَستخدِم التابعين <code>wait()‎</code> و <code>notify()‎</code> ليَسمَح لخيطٍ بالتحكُّم بخيطٍ آخر. يَحِلّ البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/TowersOfHanoiGUI.java" rel="external nofollow">TowersOfHanoiGUI.java</a> مسألة أبراج هانوي التي تعرَّضنا لها بالقسم مشكلة أبراج هانوي Hanoi من مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">التعاود recursion في جافا</a>، ويُوفِّر أزرارًا تَسمَح للمُستخدِم بالتحكُّم بتنفيذ <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1313/" rel="">الخوارزمية</a>. يَستطيع المُستخدِم مثلًا النقر على زر "Next Step" ليُنفِّذ خطوةً واحدةً من الحل، والتي تُحرِّك قرصًا واحدًا من كومةٍ لأخرى. عند النقر على زر "Run"، تُنفَّذ الخوارزمية أتوماتيكيًا دون تدخُّل المُستخدِم، ويتبدَّل النص المكتوب على الزر من "Run" إلى "Pause". عند النقر على "Pause"، يتوقَّف التشغيل التلقائي. يُوفِّر البرنامج الزر "Start Over"، الذي يُلغي الحل الحالي، ويعيد المسألة إلى حالتها الابتدائية. تَعرِض الصورة التالية شكل البرنامج بإحدى خطوات الحل، ويُمكِنك رؤية الأزرار المذكورة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="92286" href="https://academy.hsoub.com/uploads/monthly_2022_02/002Towers_Of_Hanoi_Gui.png.b96292d695574983c93f0c03aeb20bd6.png" rel=""><img alt="002Towers_Of_Hanoi_Gui.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92286" data-unique="wxppxb27z" src="https://academy.hsoub.com/uploads/monthly_2022_02/002Towers_Of_Hanoi_Gui.png.b96292d695574983c93f0c03aeb20bd6.png"></a>
</p>

<p>
	يوجد خيطان بهذا البرنامج؛ حيث يُنفِّذ الأول خوارزميةً <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">تعاودية recursive</a> لحلّ المسألة؛ ويُعالِج الآخر الأحداث الناتجة عن أفعال المُستخدِم. عندما ينقر المُستخدِم على أحد الأزرار، تُستدعَى إحدى التوابع بخيط معالجة الأحداث، ولكن من يَستجِيب فعليًا للحدث هو الخيط المُنفِّذ للتعاود؛ فقد يُنفِّذ مثلًا خطوةً واحدةً من الحل أو يبدأه من جديد. لا بُدّ أن يُرسِل خيط معالجة الأحداث نوعًا من الإشارة إلى خيط الحل من خلال ضبط قيمة متغيرٍ يتشاركه الخيطان. اسم هذا المتغير بالبرنامج هو <code>status</code>، وقيمه المُحتمَلة هي الثوابت <code>GO</code> و <code>PAUSE</code> و <code>STEP</code> و <code>RESTART</code>.
</p>

<p>
	عندما يُعدِّل خيط معالجة الأحداث قيمة ذلك المتغيّر، لا بُدّ أن يلاحظ خيط الحل القيمة الجديدة للمتغير، ويستجيب على أساسها؛ فإذا كانت قيمة <code>status</code> هي <code>PAUSE</code>، لا بُدّ أن يتوقَّف الخيط بانتظار نَقْر المُستخدِم على زر "Run" أو "Next Step"، ويُمثِّل ذلك الحالة المبدئية عند بدء البرنامج؛ أما إذا نقر المُستخدِم على زر "Next Step"، يَضبُط خيط معالجة الأحداث قيمة <code>status</code> إلى "STEP"، وبالتتابع، لا بُدّ أن يلاحِظ خيط الحل القيمة الجديدة، ويستجيب بتنفيذ خطوةٍ واحدةٍ من الحل، ثم يعيد ضَبْط قيمة <code>status</code> إلى <code>PAUSE</code> مرةً أخرى.
</p>

<p>
	إذا نقر المُستخدِم على زر "Run"، تُضبَط قيمة <code>status</code> إلى "GO"، وينبغي أن يُنفِّذ خيط الحل <a href="https://academy.hsoub.com/programming/advanced/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA-r1282/" rel="">الخوارزمية</a> أتوماتيكيًا؛ وإذا نقر المُستخدِم على زر "Pause" بينما الحل مُشغَّل، تُضبَط قيمة <code>status</code> إلى "PAUSE"، وينبغي أن يعود خيط الحل إلى حالة الإيقاف؛ أما إذا نقر المُستخدِم على زر "Start Over"، يَضبُط خيط معالجة الأحداث قيمة المُتغيّر <code>status</code> إلى "RESTART"، ولا بُدّ أن يُنهِي خيط الحل حلّه الحالي.
</p>

<p>
	ما يُهمّنا بهذا المثال هو الحالة التي يتوقَّف خلالها خيط الحل؛ حيث يكون الخيط نائمًا في تلك الحالة، ولا يكون بإمكانه رؤية القيمة الجديدة للمتغير <code>status</code> إلا إذا أيقظناه. سنَستخدِم التابع <code>wait()‎</code> بخيط الحل لجعله ينام، وسنَستخدِم التابع <code>notify()‎</code> بخيط معالجة الأحداث عندما نُعدِّل قيمة المتغير <code>status</code> لكي نُوقِظ خيط الحل. تعرض الشيفرة التالية التوابع التي تَستجيب لحدث النقر على الأزرار. عندما ينقر المُستخدِم على زر معين، يُعدِّل التابع المقابل لذلك الزر قيمة المتغير <code>status</code>، ثم يَستدعِي التابع <code>notify()‎</code> لكي يُوقِظ خيط الحل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_49" style="">
<span class="pln">synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doStopGo</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> GO</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// التحريكة مُشغَّلة. أوقفها</span><span class="pln">
        status </span><span class="pun">=</span><span class="pln"> PAUSE</span><span class="pun">;</span><span class="pln">
        nextStepButton</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
        runPauseButton</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Run"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// Animation is paused.  Start it running.</span><span class="pln">
        status </span><span class="pun">=</span><span class="pln"> GO</span><span class="pun">;</span><span class="pln">
        nextStepButton</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">  </span><span class="com">// يُعطَّل عند تشغيل التحريكة</span><span class="pln">
        runPauseButton</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Pause"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    notify</span><span class="pun">();</span><span class="pln">  </span><span class="com">// أيقظ الخيط ليتمكَّن من رؤية الحالة الجديدة</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doNextStep</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    status </span><span class="pun">=</span><span class="pln"> STEP</span><span class="pun">;</span><span class="pln">
    notify</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doRestart</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    status </span><span class="pun">=</span><span class="pln"> RESTART</span><span class="pun">;</span><span class="pln">
    notify</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لاحِظ أن تلك التوابع متزامنة لتَسمَح باستدعاء <code>notify()‎</code>. تذكَّر أنه لا بُدّ للخيط المُستدعِي للتابع <code>notify()‎</code> ضمن كائنٍ معين أن يكون قد حَصَل على قفل المزامنة المُرتبِط بذلك الكائن. في هذه الحالة، يكون كائن المزامنة هو <code>this</code>. تُعدّ المزامنة ضروريةً لأنه من الممكن أن تحدث حالات التسابق نظرًا لإمكانية خيط الحل أن يُعدِّل قيمة المتغير <code>status</code>.
</p>

<p>
	يَستدعِي خيط الحل تابعًا اسمه <code>checkStatus()‎</code> ليَفحَص قيمة <code>status</code>؛ فإذا كانت قيمة <code>status</code> تُساوِي "PAUSE"، يَستدعِي ذلك التابع بدوره التابع <code>wait()‎</code> مما يؤدي إلى توقُّف خيط الحل إلى حين استدعاء خيط معالجة الأحداث للتابع <code>notify()‎</code>. لاحِظ أن التابع <code>checkStatus()‎</code> يُبلِّغ عن استثناءٍ من النوع <code>IllegalStateException</code> إذا كانت قيمة <code>status</code> تُساوِي "RESTART":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6092_51" style="">
<span class="pln">synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> checkStatus</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> PAUSE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            wait</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="com">// بالوصول إلى تلك النقطة، تكون الحالة‫ RUN أو STEP أو RESTART</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> RESTART</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IllegalStateException</span><span class="pun">(</span><span class="str">"Restart"</span><span class="pun">);</span><span class="pln">
    </span><span class="com">// بالوصول إلى تلك النقطة، تكون الحالة‫ RUN أو STEP وينبغي أن يستمر الحل</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَضبُط التابع <code>run()‎</code> الخاص بخيط الحل الحالة المبدئية للمسألة، ثم يَستدعِي التابع <code>solve()‎</code> لحلها، كما يُنفِّذ حلقةً لا نهائية ليتمكَّن من حل المسألة عدة مرات. يَستدعِي التابع <code>run()‎</code> التابع <code>checkStatus()‎</code> قبل أن يبدأ الحل، ويَستدعِي التابع <code>solve()‎</code> التابع <code>checkStatus()‎</code> بعد كل حركة. إذا بلَّغ التابع <code>checkStatus()‎</code> عن استثناءِ من النوع <code>IllegalStateException</code>، يُنهَى استدعاء <code>solve()‎</code> مبكرًا. كنا قد استخدمنا نفس طريقة التبليغ عن استثناء لإنهاء خوارزميةٍ تعاوديةٍ من قبل بالقسم التعاود داخل الخيوط من المقال السابق.
</p>

<p>
	يُمكِنك الإطلاع على الشيفرة الكاملة للبرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/TowersOfHanoiGUI.java" rel="external nofollow">TowersOfHanoiGUI.java</a> لترى الطريقة التي دمجنا بها جميع تلك الأجزاء إلى البرنامج النهائي، وسيُعينك فهمه على تعلُم طريقة استخدام <code>wait()‎</code> و <code>notify()‎</code> مباشرةً.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c12/s3.html" rel="external nofollow">Section 3: Threads and Parallel Processing</a> من فصل Chapter 12: Threads and Multiprocessing من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/" rel="">البرمجة باستخدام الخيوط threads في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">مقدمة إلى الخيوط Threads في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel="">كيفية إنشاء عدة خيوط وفهم التزامن في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1485</guid><pubDate>Thu, 17 Mar 2022 16:01:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x62E;&#x64A;&#x648;&#x637; threads &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1484/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62107e94ab951_--2.png.478bf8e36b02733e98251388bb27b405.png" /></p>

<p>
	تضيف الخيوط threads مستوًى جديدًا من التعقيد إلى <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r662/" rel="">البرمجة</a>، ولكنها مهمةٌ وستصبح أساسيةً بالمستقبل، ولذلك لا بُدّ أن يَطلِّع كل مبرمجٍ على بعض <a href="https://wiki.hsoub.com/Design_Patterns" rel="external">أنماط التصميم</a> design pattern الأساسية المُستخدَمة مع الخيوط، حيث سنفحص بهذا المقال بعض التقنيات البسيطة وسنبني عليها بالأقسام التالية.
</p>

<h2>
	الخيوط والمؤقتات ومكتبة جافا إف إكس
</h2>

<p>
	يُمكِننا استخدام الخيوط لتنفيذ مهمةٍ معينةٍ تنفيذًا دوريًا، وهو ما يُعدّ أمرًا بسيطًا لدرجة وجود أصنافٍ مُتخصِّصة لتنفيذ تلك المهمة، ولقد تعاملنا مع إحداها بالفعل، وهو الصنف <code>AnimationTimer</code> المُعرَّف بحزمة <code>javafx.animation</code>، التي درسناها بالقسم الفرعي الصنف AnimationTimer من المقال <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-r1147/" rel="">تعرف على أهم الأحداث والتعامل معها في مكتبة جافا إف إكس JavaFX</a> حيث يستدعِي ذلك الصنف تابعه <code>handle()‎</code> دوريًا بمعدل 60 مرةٍ لكل ثانية. في الواقع، كان اِستخدَام الخيوط ضروريًا لتنفيذ العمليات المشابهة قبل أن تتوفَّر المؤقتات.
</p>

<p>
	لنفترض أننا نريد فعل شيءٍ مشابه باستخدام خيط، كأن نَستدعِي برنامجًا فرعيًا subroutine على فتراتٍ دورية، مثل 30 مرةٍ لكل ثانية. سيُنفِّذ تابع الخيط <code>run()‎</code> <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%83%D8%AA%D9%84-blocks-%D9%88%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A7%D8%AA-loops-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D8%B9%D8%A7%D8%AA-branches-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1062/" rel="">حلقة تكرار loop</a>، سيتوقَّف خلالها الخيط لمدة "30 ميللي ثانية"، ثم سيستدعِي بعدها البرنامج الفرعي. تُنفِّذ الشيفرة التالية ذلك باستخدِام <code>Thread.sleep()‎</code> -الذي ناقشناه بقسم العمليات على الخيوط من المقال السابق <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel="">مقدمة إلى الخيوط Threads في جافا</a>- ضمن صنفٍ متداخل nested:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_19" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Animator</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">30</span><span class="pun">);</span><span class="pln">
           </span><span class="pun">}</span><span class="pln">
           </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="pun">}</span><span class="pln">
           callSubroutine</span><span class="pun">();</span><span class="pln">
       </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنُنشِئ الآن كائنًا ينتمي إلى ذلك الصنف، ونَستدعِي تابعه <code>start()‎</code>، مع الملاحظة بأنه لن يكون هناك أي طريقةٍ لإيقاف الخيط بعد تشغيله؛ وإنما يُمكِننا إيقاف حلقة التكرار عندما تساوي قيمة متغيرٍ متطايرٍ volatile منطقي معين وليكن اسمه هو <code>terminate</code> القيمة <code>true</code> كما ناقشنا بقسم المتغيرات المتطايرة من المقال السابق. إذا أردنا تشغيل التحريكة animation مرةً أخرى بعد إيقافها، فسنضطّر لإنشاء كائنٍ جديدٍ من النوع <code>Thread</code>؛ نظرًا لأنه من الممكن تنفيذ تلك الكائنات مرةً واحدةً فقط. سنناقش بالقسم التالي بعض التقنيات المُستخدَمة للتحكم بالخيوط.
</p>

<p>
	تختلف الخيوط عن المؤقتات جزئيًا فيما يتعلّق بالتحريكات؛ حيث لا يفعل الخيط الذي يَستخدِمه الصنف <code>AniamtionTimer</code> والمُعرَّف بمكتبة جافا إف إكس أكثر من مجرد استدعاء البرنامج <code>handle()‎</code> مرةً بعد أخرى، والذي يُنفَّذ ضمن خيط تطبيق جافا إف إكس المسؤول عن إعادة رسم مكوِّنات الواجهة والإستجابة لما يفعله المُستخدِم. يُعدّ ذلك أمرًا مهمًا لأن مكتبة جافا إف إكس ليست آمنةً خيطيًا thread-safe؛ بمعنى أنها لا تَستخدِم المزامنة synchronization لتجنُّب حالات التسابق race conditions الممكن حدوثها بين الخيوط التي تحاول الوصول إلى كلٍ من مكوِّنات واجهة المُستخدِم الرسومية GUI ومتغيرات الحالة الخاصة بها. لا يُمثِّل ذلك مشكلةً بشرط أن يحدث كل شيء ضمن خيط التطبيق.
</p>

<p>
	في المقابل، قد تَنشَأ مشكلةٌ إذا حاول خيطٌ آخر تعديل إحدى مُكوِّنات الواجهة أو إحدى المتغيرات المُستخدَمة بخيط واجهة المُستخدِم الرسومية، وعندها قد يكون اِستخدَام المزامنة حلًا مناسبًا مع أن اِستخدَام الصنف <code>AnimationTimer</code> -إن كان ذلك ممكنًا- عادةً ما يكون الحل الأمثل؛ ولكن يُمكِنك استخدام <code>Platform.runLater()‎</code>، إذا كنت مضطّرًا لاستخدام خيطٍ منفصل.
</p>

<p>
	تحتوي حزمة <code>javafx.application</code> على الصنف <code>Platform</code> الذي يتضمَّن التابع الساكن <code>Platform.runLater(r)‎</code>؛ حيث يَستقبِل هذا التابع كائنًا من النوع <code>Runnable</code>، أي نفس الواجهة interface المُستخدَمة لإنشاء الخيوط على أنه معاملٌ بإمكاننا اِستدعائه من أي خيط. تتلّخص مسؤولية ذلك التابع في تسليم <code>r</code> إلى خيط تطبيق جافا إف إكس لتنفيذه، ثم يعود مباشرةً دون أن ينتظر انتهاء تنفيذ <code>r</code>. بعد ذلك، يَستدعِي خيط التطبيق التابع <code>r.run()‎</code> بعد أجزاءٍ من الثانية أو حتى على الفور إذا لم يَكُن الحاسوب مُنشغِّلًا بتنفيذ شيءٍ آخر.
</p>

<p>
	تُنفَّذ الأصناف المُنفِّذة للواجهة <code>Runnable</code> بنفس ترتيب تَسلُمها، ونظرًا لأنها تُنفَّذ داخل خيط التطبيق، يُمكِنها معالجة واجهة المُستخدِم الرسومية معالجةً آمنةً بدون مزامنة. يُمرَّر معامل التابع <code>Platform.runLater()‎</code> عادةً مثل تعبير لامدا lambda expression من النوع <code>Runnable</code>. سنَستخدِم <code>Platform.runLater()‎</code> بعدة أمثلة خلال هذا المقال وما يليه.
</p>

<p>
	سنفحص الآن المثال التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/RandomArtWithThreads.java" rel="external nofollow">RandomArtWithThreads.java</a> الذي يَستخدِم خيطًا للتحكُّم بتحريكةٍ بسيطةٍ جدًا. لا يفعل الخيط بهذا المثال أكثر من مجرد استدعاء التابع <code>redraw()‎</code> كل ثانيتين، الذي يُعيد رسم محتويات الحاوية canvas؛ واستخدام التابع <code>Platform.runLater()‎</code> لتنفيذ <code>redraw()‎</code> ضمن خيط التطبيق. يستطيع المُستخدِم الضغط على زر لبدء التحريكة وإيقافها. يُنشَأ خيطٌ جديدٌ بكل مرة تبدأ خلالها التحريكة، ويُضبَط متغيرٌ منطقيٌ متطايرٌ اسمه <code>running</code> إلى القيمة <code>false</code> عندما يُوقِف المُستخدِم التحريكة إشارةً للخيط بأن عليه أن يتوقف، كما ناقشنا بالمقال المتغيرات المتطايرة من المقال السابق. يُعرِّف الخيط بواسطة الصنف التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_21" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Runner</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> redraw</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">2000</span><span class="pun">);</span><span class="pln">  </span><span class="com">// انتظر ثانيتين قبل إعادة رسم الشاشة</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	التعاود داخل الخيوط
</h2>

<p>
	إذا كان الخيط يُنفِّذ خوارزميةً تعاوديةً recursive (ناقشنا التعاود في مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">التعاود recursion في جافا</a>)، وكنت تريد إعادة رسم الواجهة عدة مرات أثناء حدوث التعاود؛ فقد تضطّر لاستخدام خيطٍ منفصلٍ للتحكُّم بالتحريكة. من الصعب تقسيم خوارزمية تعاودية إلى سلسلةٍ من استدعاءات التوابع داخل مؤقت، فمن البديهي أكثر استدعاء تابعٍ تعاودي واحدٍ لإجراء التعاود، وهو أمرٌ سهل إنجازه ضمن خيط.
</p>

<p>
	سنفحص المثال التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/QuicksortThreadDemo.java" rel="external nofollow">QuicksortThreadDemo.java</a> الذي يَرسِم تحريكةً تُوضِح طريقة عمل خوارزمية QuickSort التعاودية لترتيب المصفوفات. ستحتوي المصفوفة في هذا المثال على ألوان، وسيكون الهدف هو ترتيبها وفقًا لسلّم الألوان المعروف من الأحمر إلى البنفسجي. يَسمَح البرنامج للمُستخدِم أيضًا بالنقر على زر "Start" لبدء العملية، وعندها تُرتَّب الألوان ترتيبًا عشوائيًا، ثم تُستدعَى خوارزمية QuickSort لترتيبها وتُعرَض العملية بحركة بطيئة. يتبدَّل زر "Start" أثناء عملية الترتيب إلى "Finish" ليَسمَح للمُستخدِم بإيقاف عملية الترتيب قبل انتهائها. في الواقع، من الممتع مشاهدة خرج هذا البرنامج، ولربما يساعدك حتى على فهم طريقة عمل خوارزمية QuickSort على نحوٍ أفضل، لذلك عليك أن تُجرِّب تشغيله.
</p>

<p>
	ينبغي أن تتغيّر الصورة المعروضة بالحاوية في هذا البرنامج بكل مرةٍ تُجرِي خلالها الخوارزمية تعديلًا على المصفوفة. لاحِظ أن المصفوفة تتغيّر بخيط التحريكة بينما لا بُدّ من إجراء التغيير المقابل على الحاوية بخيط <a href="https://academy.hsoub.com/programming/java/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A8%D8%B3%D9%8A%D8%B7-r1145/" rel="">تطبيق جافا إف إكس</a> باستخدام <code>Platform.runLater()‎</code> كما ناقشنا بالأعلى. بكل مرة يُستدعى خلالها <code>Platform.runLater()‎</code>، يتوقف الخيط لمدة "100 ميللي ثانية" ليَسمَح لخيط التطبيق بتنفيذ المعامل المُمرَّر من النوع <code>Runnable</code> وليتمكَّن المُستخدِم من مشاهدة التعديلات. هناك أيضًا توقُّفٌ أطول بما يصِل إلى ثانيةٍ كاملة بعد ترتيب عناصر المصفوفة عشوائيًا مباشرةً وقبل بدء عملية الترتيب الفعلي. يُعرِّف الصنف <code>QuicksortThreadDemo</code> التابع <code>delay()‎</code> الذي يجعل الخيط المُستدِعي له يتوقَّف لفترة معينة، نظرًا لأن الشيفرة تتوقَّف بأكثر من مكان.
</p>

<p>
	والآن، كيف نُنفِّذ شيفرة الزر "Finish" المسؤولة عن إيقاف عملية الترتيب وإنهاء الخيط؟ في الواقع، يؤدي النقر على هذا الزر إلى ضبط قيمة المتغير المنطقي المتطاير <code>running</code> إلى القيمة <code>false</code> إشارةً للخيط بأنه عليه الانتهاء. تَكْمُن المشكلة في إمكانية النقر عليه بأي لحظة، حتى لو كان البرنامج منهمكًا بتنفيذ <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1063/" rel="">الخوارزمية</a> وبمستوًى منخفضٍ جدًا من التعاود.
</p>

<p>
	لا بُدّ أن تعود جميع استدعاءات التوابع <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF%D9%8A%D8%A9-recursion-r1387/" rel="">التعاودية</a> لنتمكّن من إنهاء الخيط، ويُعد التبليغ عن <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%A7%D8%AA-exceptions-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1309/" rel="">استثناء exception</a> إحدى أبسط الطرق التي يُمكنِها تحقيق ذلك. يُعرِّف الصنف <code>QuickSortThreadDemo</code> صنف استثناءٍ جديد اسمه <code>ThreadTerminationException</code> لهذا الغرض، ويفحص التابع <code>delay()‎</code> قيمة المُتغيّر <code>running</code>؛ فإذا كانت مساويةً للقيمة <code>false</code>، سيُبلِّغ عن استثناءٍ تسبَّب بإنهاء الخوارزمية التعاودية، وبالتتابع خيط التحريكة ذاته. ألقِ نظرةً على تعريف التابع <code>delay()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_28" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> delay</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> millis</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln"> running</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ThreadTerminationException</span><span class="pun">();</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="pln">millis</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln"> running</span><span class="pun">)</span><span class="pln"> </span><span class="com">// افحصها مرة أخرى فربما تكون قد تغيرت أثناء توقُّف الخيط</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ThreadTerminationException</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَلتقِط تابع الخيط <code>run()‎</code> الاستثناء المنتمي للصنف <code>ThreadTerminationException</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_30" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Runner</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> hue</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// املأ المصفوفة باستخدام الفهارس</span><span class="pln">
            hue</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> hue</span><span class="pun">.</span><span class="pln">length</span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">--)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
               </span><span class="com">// رتّب المصفوفة عشوائيًا</span><span class="pln">
            </span><span class="typ">int</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pun">)((</span><span class="pln">i</span><span class="pun">+</span><span class="lit">1</span><span class="pun">)*</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">());</span><span class="pln">
            </span><span class="typ">int</span><span class="pln"> temp </span><span class="pun">=</span><span class="pln"> hue</span><span class="pun">[</span><span class="pln">r</span><span class="pun">];</span><span class="pln">
            hue</span><span class="pun">[</span><span class="pln">r</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> hue</span><span class="pun">[</span><span class="pln">i</span><span class="pun">];</span><span class="pln">
               </span><span class="com">// 2</span><span class="pln">
            setHue</span><span class="pun">(</span><span class="pln">i</span><span class="pun">,</span><span class="pln">temp</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            delay</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">);</span><span class="pln">  </span><span class="com">// انتظر ثانية قبل بدء عملية الترتيب</span><span class="pln">
            quickSort</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln">hue</span><span class="pun">.</span><span class="pln">length</span><span class="pun">-</span><span class="lit">1</span><span class="pun">);</span><span class="pln">  </span><span class="com">// رتّب المصفوفة بالكامل</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">ThreadTerminationException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// ألغى المُستخدِم عملية الترتيب</span><span class="pln">
                </span><span class="com">// 3</span><span class="pln">
            </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> drawSorted</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        finally </span><span class="pun">{</span><span class="pln">
            running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">  </span><span class="com">// 4</span><span class="pln">
            </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> startButton</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Start"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث أن:
</p>

<ul>
<li>
		[1]: يُعرِّف هذا الصنف خيطًا يُنفِّذ خوارزمية <code>QuickSort</code> <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">التعاودية</a>؛ حيث يبدأ الخيط بخلط عناصر المصفوفة <code>hue</code> عشوائيًا، ثم يَستدعِي التابع <code>quickSort()‎</code> لترتيبها بالكامل. إذا توقَّف التابع <code>quickSort</code> نتيجة استثناء من النوع <code>ThreadTerminationException</code> -يحدُث إذا نقر المُستخدِم على زر "Finish"-، يُعيد الخيط المصفوفة إلى حالتها المُرتَّبة قبل أن ينتهي؛ وبالتالي سواءٌ ألغى المُستخدِم عملية الترتيب أم لا، تكون المصفوفة مرتبةً بنهاية الخيط. في جميع الحالات، يُضبَط نص الزر إلى "Start" بالنهاية.
	</li>
	<li>
		[2]: التعليمة الأخيرة التي ينبغي إنجازها ضمن الحلقة هي <code>hue<em> = temp</em></code><em>. لن تتغير قيمة </em><code><em>hue</em><em><em>‎</em></em></code><em><em> بعد ذلك، ولذلك تُنجز عملية الإسناد باستدعاء التابع </em></em><code><em><em>setHue(i,temp)‎</em></em></code><em><em> الذي سيُبدِّل القيمة الموجودة بالمصفوفة، كما أنه يَستِخدِم </em></em><code><em><em>Platform.runLater()‎</em></em></code><em><em> لتغيير لون الشريط رقم </em></em><code><em><em>i</em></em></code><em><em> بالحاوية.
	</em></em>
</li>
<em><em>
	</em></em><li>
<em><em>
		[3]: ضع الألوان بصورةٍ مرتبة. يرسم التابع </em></em><code><em><em>drawSorted()‎</em></em></code><em><em> ألوان جميع الشرائط بالترتيب.
	</em></em>
</li>
<em><em>
	</em></em><li>
<em><em>
		[4]: تأكَّد من أن </em></em><code><em><em>running</em></em></code><em><em> يُساوِي </em></em><code><em><em>false</em></em></code><em><em>. يكون ذلك ضروريًا فقط إذا انتهى الخيط طبيعيًا.
	</em></em>
</li>
<em><em>
</em></em>
</ul>
<p><em><em>
	يَستخدِم البرنامج المتغير </em></em><code><em><em>runner</em></em></code><em><em> من النوع </em></em><code><em><em>Runner</em></em></code><em><em> لتمثيل الخيط المسؤول عن عملية الترتيب. عندما ينقر المُستخدِم على الزر "Start"، تُنفَّذ الشيفرة التالية لإنشاء الخيط وتشغيله:
</em></em></p>
<em><em>

</em></em><pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_32" style="">
<span class="pln">startButton</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Finish"</span><span class="pun">);</span><span class="pln">
runner </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Runner</span><span class="pun">();</span><span class="pln">
running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اضبط قيمة الإشارة قبل تشغيل الخيط</span><span class="pln">
runner</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span></pre>
<em><em>

</em></em><p><em><em>
	لا بُدّ من ضبط قيمة متغير الإشارة </em></em><code><em><em>running</em></em></code><em><em> إلى القيمة </em></em><code><em><em>true</em></em></code><em><em> قبل بدء الخيط؛ لأنه لو كان يحتوي على القيمة </em></em><code><em><em>false</em></em></code><em><em> بالفعل عند بدء الخيط، فلربما سيرى الخيط تلك القيمة بمجرد بدءه، ويُفسِّرها على كونها إشارةً للتوقُّف قبل أن يفعل أي شيء. تذكَّر أنه عند استدعاء </em></em><code><em><em>runner.start()‎</em></em></code><em><em>، يبدأ الخيط </em></em><code><em><em>runner</em></em></code><em><em> بالعمل على التوازي مع الخيط المُستدعِي له.
</em></em></p>
<em><em>

</em></em><p><em><em>
	عندما ينقر المُستخدِم على زر "Finish"، تُضبَط قيمة </em></em><code><em><em>running</em></em></code><em><em> إلى القيمة </em></em><code><em><em>false</em></em></code><em><em> إشارةً للخيط بأن عليه الانتهاء، ولكن ماذا لو كان الخيط نائمًا في تلك اللحظة؟ في تلك الحالة لا بُدّ أن يستيقظ الخيط أولًا حتى يتمكَّن من الإستجابة لتلك الإشارة؛ أما إذا أردنا أن نجعله يستجيب بصورةٍ أسرع، يُمكِننا استدعاء التابع </em></em><code><em><em>runner.interrupt()‎</em></em></code><em><em> لإيقاظ الخيط إذا كان نائمًا. لا يؤثر هذا على البرنامج من الناحية العملية، ولكنه يجعل استجابة البرنامج أسرع على نحوٍ ملحوظ بالأخص إذا نقر المُستخدِم على زر "Finish" بعد النقر على زر "Start" مباشرةً عندما ينام الخيط لمدة ثانيةٍ كاملة.
</em></em></p>
<em><em>

</em></em><h2><em><em>
	استخدام الخيوط بالعمليات المنفذة بالخلفية
</em></em></h2>
<em><em>

</em></em><p><em><em>
	إذا أردنا أن تكون استجابة برامج </em></em><a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel=""><em><em>واجهة المُستخدِم الرسومية GUI</em></em></a><em><em> سريعة، أي تستجيب للأحداث events بمجرد وقوعها تقريبًا، لا بُدّ أن تُنهِي توابع معالجة الأحداث الموجودة بالبرنامج عملها بسرعة. تُخزَّن الأحداث برتل queue أثناء وقوعها، ولا يستطيع الحاسوب الاستجابة لحدثٍ معين قبل أن تُنهِي توابع معالجة الأحداث السابقة له عملها. يَعنِي ذلك أنه ينبغي للأحداث الانتظار أثناء تنفيذ الحاسوب لمُعالِج حدثٍ معين؛ وإذا استغرق مُعالِج حدثٍ معين فترةً طويلة لتنفيذ عمله، ستَجْمُد freeze واجهة المُستخدِم خلال تلك الفترة، وهو ما يُضايق المُستخدِم بالأخص إذا استمر لأكثر من جزءٍ من الثانية.
</em></em></p>
<em><em>

</em></em><p><em><em>
	تستطيع الحواسيب العصرية لحسن الحظ إنجاز الكثير من العمليات الضخمة خلال جزءٍ من الثانية. ومع ذلك، هناك بعض العمليات الضخمة للغاية لدرجة لا يُمكِن تنفيذها بمعالجات الأحداث event handlers (أو بتمرير مُنفَّذات للواجهة </em></em><code><em><em>Runnable</em></em></code><em><em> إلى </em></em><code><em><em>Platform.runlater()‎</em></em></code><em><em>). ويكون من الأفضل في تلك الحالة تنفيذ تلك العمليات بخيطٍ آخر منفصل يَعمَل على التوازي مع خيط معالجة الأحداث، وهذا يَسمَح للحاسوب بالاستجابة إلى الأحداث الأخرى في نفس الوقت الذي يُنفَّذ خلاله تلك العملية، ويُقال أن العملية "تُنفَّذ بالخلفية background".
</em></em></p>
<em><em>

</em></em><p><em><em>
	يختلف تطبيق الخيوط في هذا المثال عن المثال السابق؛ فعندما يُستخدَم الخيط للتحكُّم بتحريكة، فإنه فعليًا لا يَفعَل سوى القليل، إذ عليه فقط أن يستيقظ كل عدة ثواني ليُجرِي قليلًا من العمليات المتعلّقة بتحديث متغيرات الحالة state variables لإطار التحريكة التالي ومن ثَمّ رَسْمه. يُوفِّر ذلك وقتًا كافيًا لخيط تطبيق جافا إف إكس، ويَسمَح له بإجراء أي إعادة رسمٍ ضرورية لمُكوِّنات واجهة المُستخدِم الرسومية، وكذلك معالجة أي أحداثٍ اخرى.
</em></em></p>
<em><em>

</em></em><p><em><em>
	عندما نُنفِّذ عمليةٌ معينة بالخلفية ضمن خيط، فإننا نريد إبقاء الحاسوب مُنشغِلًا بتنفيذ تلك العملية بأقصى ما يمكن، ولكن قد يتسابق هذا الخيط مع خيط التطبيق على زمن المعالجة، وعندها يبقى تعطُّل معالجة الأحداث بالأخص إعادة الرسم ممكنًا إذا لم ننتبه كفاية. يُمكِننا لحسن الحظ استخدام أولويات priorities للخيوط لنتجنَّب تلك المشكلة، حيث يُمكِننا ضبط الخيط المسؤول عن تنفيذ العملية بالخلفية بحيث يَعمَل بأولويةٍ أقل من أولوية خيط معالجة الأحداث، وسنضمَن بذلك معالجة الأحداث بأسرع ما يمكن، وسيَحظَى بنفس الوقت الخيط الآخر بأي زمن معالجةٍ إضافي. تستغرق معالجة الأحداث وقتًا قصيرًا للغاية عمومًا، وبالتالي سيُستغَل غالبية زمن المعالجة بتنفيذ العملية المُشغَّلة بالخلفية بنفس الوقت الذي ستستجيب فيه الواجهة بسرعة. ناقشنا أولوية الخيوط في قسم العمليات على الخيوط من المقال السابق.
</em></em></p>
<em><em>

</em></em><p><em><em>
	يُعدّ البرنامج </em></em><a href="http://math.hws.edu/javanotes/source/chapter12/BackgroundComputationDemo.java" rel="external nofollow"><em><em>BackgroundComputationDemo.java</em></em></a><em><em> مثالًا توضيحيًا على معالجة العمليات بالخلفية. يُنشِئ هذا البرنامج صورةً يستغرِق تحديد ألوان بكسلاتها وقتًا طويلًا نوعًا ما. تمثّل تلك الصورة قطعةً من شكلٍ هندسيٍ معروف باسم "مجموعة ماندلبرو Mandelbrot set"، وسنستخدِم تلك الصورة بعدة أمثلة خلال هذا المقال.
</em></em></p>
<em><em>

</em></em><p><em><em>
	يتشابه البرنامج "BackgroundComputationDemo" مع برنامج "QuicksortThreadDemo" الذي ناقشناه بالأعلى، حيث تُجرَى العمليات ضمن خيطٍ مُعرَّفٍ بواسطة </em></em><a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D8%AA%D8%AF%D8%A7%D8%AE%D9%84%D8%A9-nested-classes-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1115/" rel=""><em><em>صنفٍ مُتداخِل</em></em></a><em><em> اسمه </em></em><code><em><em>Runner</em></em></code><em><em>، وسنَستخدِم متغيرًا متاطيرًا اسمه </em></em><code><em><em>running</em></em></code><em><em> للتحكُّم بالخيط؛ فإذا كان المُتغيّر يساوي </em></em><code><em><em>false</em></em></code><em><em>، ينبغي أن ينتهي الخيط. يُوفِّر البرنامج زرًا لبدء العملية وإنهائها. بخلاف البرنامج السابق، سيَعمَل الخيط باستمرار دون أن ينام. بعد انتهاء الخيط من حساب كل صف من البسكلات، سيَستدعِي التابع </em></em><code><em><em>Platform.runLater()‎</em></em></code><em><em> لينسخ تلك البكسلات إلى الصورة المعروضة على الشاشة، وسيتمكَّن بذلك المُستخدِم من مشاهدة التحديثات الناتجة عن العمليات المُجرَاة، وسيشاهد الصورة أثناء تكوُّنها صفًا بعد آخر.
</em></em></p>
<em><em>

</em></em><p><em><em>
	عندما ينقر المُستخدِم على زر "Start"، يُنشَأ الخيط المسؤول عن عملية المعالجة، والذي لا بُدّ من ضبطه ليَعمَل بأولويةٍ أقل من أولوية خيط تطبيق جافا إف إكس. نظرًا لوقوع الشيفرة المسؤولة عن إنشاء ذلك الخيط ضمن خيط التطبيق، يُمكِننا ضبط أولوية الخيط المُنشَا بحيث تكون أقل بمقدار الواحد من أولوية الخيط المُشغَّل. تذكَّر أنه من الضروري ضبط تلك الأولوية داخل تعليمة </em></em><code><em><em>try..catch</em></em></code><em><em>؛ فإذا حدث خطأ أثناء ذلك، نَضمَن استمرار البرنامج، وإن لم يَكُن بنفس السلاسة التي كان سيَعمَل بها لو كانت الأولوية قد ضُبطَت صحيحًا. تُنشِئ الشيفرة التالية الخيط، وتُشغِّله:
</em></em></p>
<em><em>

</em></em><pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_38" style="">
<span class="pln">runner </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Runner</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    runner</span><span class="pun">.</span><span class="pln">setPriority</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">currentThread</span><span class="pun">().</span><span class="pln">getPriority</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error: Can't set thread priority: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اضبط قيمة الإشارة قبل تشغيل الخيط</span><span class="pln">
runner</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span></pre>
<em><em>

</em></em><p><em><em>
	على الرغم من عمل البرنامج BackgroundComputationDemo جيدًا، إلا أن هناك مشكلةً واحدةً وهي أن هدفنا هو إتمام العملية بأسرع وقتٍ ممكن من خلال استغلال ما هو مُتوفّرٌ من زمن المعالجة. سيتمكَّن البرنامج من إنجاز ذلك الهدف إذا كان مُشغَّلًا على حاسوبٍ بمعالج واحد؛ ولكن إذا كان الحاسوب يحتوي على عدة معالجات، فإننا في الواقع نَستخدِم معالجًا واحدًا فقط لإنجاز العملية، وفي تلك الحالة، لن يكون لأولوية الخيط أي أهمية؛ فمن الممكن تشغيل كُلٍ من خيط التطبيق وخيط التحريكة على التوازي باستخدام معالجين مختلفين. سيكون من الأفضل لو تمكَّنا من استخدام جميع تلك المعالجات لإتمام العملية، وهو ما يتطلَّب معالجةً على التوازي parallel processing من خلال عدة خيوط. سننتقل إلى تلك المشكلة فيما يلي.
</em></em></p>
<em><em>

</em></em><h2><em><em>
	استخدام الخيوط في المعالجة المتعددة
</em></em></h2>
<em><em>

</em></em><p><em><em>
	سنفحص الآن البرنامج </em></em><a href="http://math.hws.edu/javanotes/source/chapter12/MultiprocessingDemo1.java" rel="external nofollow"><em><em>MultiprocessingDemo1.java</em></em></a><em><em>، الذي يختلف قليلًا عن البرنامج "BackgroundComputationDemo"؛ فبدلًا من إجراء المعالجة بخيطٍ واحد فقط، سيُقسِّم البرنامج "MultiprocessingDemo1" المعالجة على عدة خيوط. وسيَسمَح البرنامج للمُستخدِم بتخصيص عدد الخيوط المطلوب تشغيلها، وسيتولى كل خيطٍ منها المعالجة المطلوبة لجزءٍ معين من الصورة. ينبغي أن تنجز الخيوط عملها على التوازي؛ فإذا استخدمنا خيطين على سبيل المثال، فسيَحسِب الأول النصف الأعلى من الصورة؛ بينما سيَحسِب الثاني النصف السفلي. تعرض الصورة التوضيحية التالية شاشة البرنامج مع اقتراب نهاية المعالجة عند استخدام ثلاثة خيوط، حيث تُشير المساحات الرمادية إلى أجزاء الصورة غير المُعالجة بعد.
</em></em></p>
<em><em>

</em></em><p style="text-align: center;"><em><em>
	</em></em><a class="ipsAttachLink ipsAttachLink_image" data-fileid="92284" href="https://academy.hsoub.com/uploads/monthly_2022_02/001Multiprocessing_Demo1.png.bcc4c768a501363dc42c23b0e79fb071.png" rel=""><img alt="001Multiprocessing_Demo1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92284" data-unique="6fpx9qall" src="https://academy.hsoub.com/uploads/monthly_2022_02/001Multiprocessing_Demo1.png.bcc4c768a501363dc42c23b0e79fb071.png"></a><em><em>
</em></em></p>
<em><em>

</em></em><p><em><em>
	عليك أن تُجرِّب البرنامج؛ فعند استخدام عدة خيوطٍ بحاسوبٍ متعدّد المعالجات، ستكتمل المعالجة على نحوٍ أسرع بالموازنة مع استخدام خيطٍ واحد.
</em></em></p>
<em><em>

</em></em><blockquote class="ipsQuote" data-ipsquote="">
<em><em>
	</em></em><div class="ipsQuote_citation"><em><em>
		اقتباس
	</em></em></div>
<em><em>

	</em></em><div class="ipsQuote_contents ipsClearfix">
<em><em>
		</em></em><p><em><em>
			 
		</em></em></p>
<em><em>

		</em></em><p><em><em>
			ملاحظة: سيَعمَل هذا البرنامج بنفس طريقة عمل برنامج المثال السابق في حالة استخدام خيط واحد.
		</em></em></p>
<em><em>

		</em></em><p><em><em>
			 
		</em></em></p>
<em><em>
	</em></em>
</div>
<em><em>
</em></em>
</blockquote>
<em><em>

</em></em><p><em><em>
	لا تُعدّ الطريقة المُستخدَمة لتقسيم المشكلة على مجموعة الخيوط بهذا المثال الطريقة الأمثل، وسنتناول بالقسم التالي إمكانية تحسين تلك الطريقة، ومع ذلك ما يزال البرنامج "MultiprocessingDemo1" مثالًا جيدًا على المعالجة المُتعدّدة.
</em></em></p>
<em><em>

</em></em><p><em><em>
	عندما ينقر المُستخدِم على زر "Start"، سيُنشِئ البرنامج عدد الخيوط المُخصَّصة ويُشغِّلها، كما سيُسنِد لكُلٍ منها جزءًا من الصورة. ألقِ نظرةً على الشيفرة التالية:
</em></em></p>
<em><em>

</em></em><pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_40" style="">
<span class="pln">workers </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Runner</span><span class="pun">[</span><span class="pln">threadCount</span><span class="pun">];</span><span class="pln">  </span><span class="com">// يحمل خيوط المعالجة</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> rowsPerThread</span><span class="pun">;</span><span class="pln">  </span><span class="com">// عدد الخيوط التي ينبغي أن يحسبها كل خيط</span><span class="pln">
rowsPerThread  </span><span class="pun">=</span><span class="pln"> height </span><span class="pun">/</span><span class="pln"> threadCount</span><span class="pun">;</span><span class="pln">  </span><span class="com">// (height = vertical size of image)</span><span class="pln">
running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اضبط قيمة الإشارة قبل تشغيل الخيوط</span><span class="pln">
threadsCompleted </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">  </span><span class="com">// Records how many of the threads have terminated.</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> threadCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> startRow</span><span class="pun">;</span><span class="pln">  </span><span class="com">// ‫الصف الأول الذي يحسبه الخيط رقم i‫</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> endRow</span><span class="pun">;</span><span class="pln">    </span><span class="com">// 1</span><span class="pln">
    startRow </span><span class="pun">=</span><span class="pln"> rowsPerThread</span><span class="pun">*</span><span class="pln">i</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i </span><span class="pun">==</span><span class="pln"> threadCount</span><span class="pun">-</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        endRow </span><span class="pun">=</span><span class="pln"> height</span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        endRow </span><span class="pun">=</span><span class="pln"> rowsPerThread</span><span class="pun">*(</span><span class="pln">i</span><span class="pun">+</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Runner</span><span class="pun">(</span><span class="pln">startRow</span><span class="pun">,</span><span class="pln"> endRow</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">setPriority</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">currentThread</span><span class="pun">().</span><span class="pln">getPriority</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    workers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>
<em><em>

</em></em><p><em><em>
	[1] الصف الأخير الذي يحسبه الخيط رقم </em></em><code><em><em>i</em></em></code><em><em>. انشِئ خيطًا وشغَِله لحساب صفوف الصورة من </em></em><code><em><em>startRow</em></em></code><em><em> إلى </em></em><code><em><em>endRow</em></em></code><em><em>. لا بُدّ أن تكون قيمة </em></em><code><em><em>endRow</em></em></code><em><em> للخيط الأخير مساويةً لرقم الصف الأخير بالصورة.
</em></em></p>
<em><em>

</em></em><p><em><em>
	أجرينا عددًا قليلًا من التعديلات لتفعيل المعالجة المتعددة إلى جانب إنشاء عدة خيوط بدلًا من خيط واحد. كما هو الحال في المثال السابق: عندما ينتهي أي خيطٍ من حساب ألوان صفٍ من البسكلات، فإنه يَستدعِي التابع </em></em><code><em><em>Platform.runLater()‎</em></em></code><em><em> ليَنسَخ الصف إلى الصورة.
</em></em></p>
<em><em>

</em></em><p><em><em>
	هناك شيءٌ واحدٌ جديد، وهو: عندما تنتهي جميع الخيوط من عملها، سيتبدّل اسم الزر من "Abort" إلى "Start Again"، وسيُعاد تفعيل القائمة التي كانت قد عُطّلَت بينما الخيوط مُشغَّلة. والآن، كيف سنعرف أن جميع الخيوط قد انتهت؟ ربما تتساءل لما لا نَستخدِم </em></em><code><em><em>join()‎</em></em></code><em><em> لننتظر انتهاء الخيوط كما فعلنا بمثالٍ سابق في قسم العمليات على الخيوط من المقال السابق؟ حيث لا يُمكِننا بالتأكيد فعل ذلك بخيط تطبيق جافا إف إكس على الأقل.
</em></em></p>
<em><em>

</em></em><p><em><em>
	سنَستخدِم في هذا المثال متغير نسخة instance variable، اسمه </em></em><code><em><em>threadsRunning</em></em></code><em><em> لتمثيل عدد </em></em><a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel=""><em><em>خيوط</em></em></a><em><em> المعالجة قيد التنفيذ؛ وعندما ينتهي كل خيطٍ من عمله، عليه استدعاء تابعٍ لإنقاص قيمة ذلك المتغير بمقدار الواحد، حيث سيُستدعَى ذلك التابع ضمن عبارة </em></em><code><em><em>finally</em></em></code><em><em> بتعليمة </em></em><code><em><em>try</em></em></code><em><em> لنتأكَّد تمامًا من تنفيذها عند انتهاء الخيط. عندما يُصبِح عدد الخيوط المُشغَّلة صفرًا، سيُحدِّث التابع حالة البرنامج على النحو المطلوب. تعرض الشيفرة التالية التابع الذي ستستدعيه الخيوط قبل انتهائها:
</em></em></p>
<em><em>

</em></em><pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_977_43" style="">
<span class="pln">synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> threadFinished</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    threadsRunning</span><span class="pun">--;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">threadsRunning </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// انتهت جميع الخيوط</span><span class="pln">
        </span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">runLater</span><span class="pun">(</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="com">// تأكَّد من صحة حالة واجهة المُستخدِم الرسومية عندما تنتهي الخيوط</span><span class="pln">
            startButton</span><span class="pun">.</span><span class="pln">setText</span><span class="pun">(</span><span class="str">"Start Again"</span><span class="pun">);</span><span class="pln">
            startButton</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
            threadCountSelect</span><span class="pun">.</span><span class="pln">setDisable</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
        running </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
        workers </span><span class="pun">=</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>
<em><em>

</em></em><p><em><em>
	لاحِظ أن التابع المُعرَّف بالأعلى </em></em><a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel=""><em><em>متزامن synchronized</em></em></a><em><em>؛ لضمان تجنُّب حالة التسابق race condition التي يُمكِنها أن تقع عند إنقاص قيمة المُتغيّر </em></em><code><em><em>threadsRunning</em></em></code><em><em>. قد يَستدعِي خيطان ذلك التابع بنفس اللحظة إذا لم نَستخدِم المزامنة، وإذا كان التوقيت دقيقًا، فربما يقرأ كلاهما نفس قيمة المتغير </em></em><code><em><em>threadsRunning</em></em></code><em><em>، ويَحسِبا نفس الإجابة بعد إنقاصه. ستقل في تلك الحالة قيمة المتغير </em></em><code><em><em>threadsRunning</em></em></code><em><em> بمقدار واحدٍ فقط وليس اثنين؛ أي أننا لم نَعُدّ خيطًا على النحو الصحيح، وعليه لن يَصِل المتغير </em></em><code><em><em>threadsRunning</em></em></code><em><em> إلى الصفر نهائيًا، وسيَعمَل البرنامج باستمرار بطريقةٍ مشابهة للقفل الميت deadlock. في الواقع، نادرًا ما تَحدث تلك المشكلة لأنها تعتمد على توقيتٍ بعينه، ولكن بالبرامج الأكبر حجمًا، تصبح تلك المشاكل خطيرةً للغاية كما أن تنقيحها debug ليس سهلًا. في المقابل، تمنع المزامنة حدوث ذلك الخطأ تمامًا.
</em></em></p>
<em><em>

</em></em><p><em><em>
	ترجمة -بتصرّف- للقسم </em></em><a href="http://math.hws.edu/javanotes/c12/s2.html" rel="external nofollow"><em><em>Section 2: Programming with Threads</em></em></a><em><em> من فصل Chapter 12: Threads and Multiprocessing من كتاب </em></em><a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow"><em><em>Introduction to Programming Using Java</em></em></a><em><em>.
</em></em></p>
<em><em>

</em></em><h2><em><em>
	اقرأ أيضًا
</em></em></h2>
<em><em>

</em></em><ul>
<li>
<em><em>
		المقال السابق: </em></em><a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/" rel=""><em><em>مقدمة إلى الخيوط Threads في جافا</em></em></a><em><em>
	</em></em>
</li>
<em><em>
	</em></em><li>
<em><em>
		</em></em><a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel=""><em><em>كيفية إنشاء عدة خيوط وفهم التزامن في جافا</em></em></a><em><em>
	</em></em>
</li>
<em><em>
	</em></em><li>
<em><em>
		</em></em><a href="https://academy.hsoub.com/programming/java/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D9%85%D8%B9%D9%85%D9%85%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1432/" rel=""><em><em>كتابة أصناف وتوابع معممة في جافا</em></em></a><em><em>
	</em></em>
</li>
<em><em>
</em></em>
</ul>
<p><em><em>
	 
</em></em></p>
]]></description><guid isPermaLink="false">1484</guid><pubDate>Fri, 25 Feb 2022 09:31:09 +0000</pubDate></item><item><title>&#x645;&#x642;&#x62F;&#x645;&#x629; &#x625;&#x644;&#x649; &#x627;&#x644;&#x62E;&#x64A;&#x648;&#x637; Threads &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-threads-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1483/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/620f7e96f2f7b_---(threads).png.0f3c9c4e6da611867d597ccec1330442.png" /></p>

<p>
	يمكن للحواسيب إنجاز عدة مهامٍ مختلفة بنفس الوقت؛ فإذا كان الحاسوب مُكوَّنًا من وحدة معالجةٍ واحدة، فإنه لا يستطيع حرفيًا إنجاز شيئين بنفس الوقت؛ ولكن ما يزال بإمكانه تحويل انتباهه بين عدة مهامٍ باستمرار. تتكوَّن معظم الحواسيب العصرية من أكثر من مجرد وحدة معالجةٍ واحدة، وبإمكانها تنفيذ عدة مهامٍ بنفس الوقت حرفيًا، وغالبًا ما تكون زيادة قدرة المعالجة للحواسيب ناتجةً عن إضافة عددٍ أكبر من المعالجات إليها بدلًا من زيادة سرعة كل معالجٍ على حدى.
</p>

<p>
	حتى تتمكّن البرامج من تحقيق الاستفادة القصوى من الحواسيب متعددة المعالجات، لا بُدّ أن تكون مُبرمَجَةً على التوازي parallel programming؛ بما يعني كتابة البرنامج بهيئة مجموعةٍ من المهام المُمكِن تنفيذها بنفس الوقت. ما تزال تقنيات البرمجة على التوازي مفيدة حتى بالحواسيب أحادية المعالج، حيث يُساعد تقسيم المشكلات إلى مجموعة من المهام على معالجة المشكلة بصورةٍ مُبسّطة.
</p>

<p>
	يُطلَق اسم <a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel="">خيط thread</a> بلغة جافا على كل مهمة، ويشير ذلك الاسم إلى "خيط التحكُّم" أو "خيط التنفيذ" الذي يعني متتالية التعليمات المُنفَّذة واحدةً تلو الأخرى؛ حيث يمتد الخيط عبر الزمن ويربط كل تعليمةٍ بما يليها من تعليمات. توجد خيوط تحكمٍ كثيرة بالبرامج مُتعدّدة الخيوط، وتَعمَل جميعًا على التوازي، لتُشكِّل "نسيج" البرنامج. يتكوَّن كل برنامج عمومًا من خيطٍ واحدٍ على الأقل؛ فعندما نُشغِّل برنامجًا معينًا <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">بآلة جافا الافتراضية Java virtual machine</a>، فإنها تُنشِئ خيطًا مسؤولًا عن تنفيذ البرنامج الرئيسي الذي يستطيع أن يُنشِئ بدوره خيوطًا أخرى قد تستمر حتى بعد انتهاء الخيط الرئيسي.
</p>

<p>
	بالنسبة لبرامج <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">واجهة المُستخدِم الرسومية GUI</a>، يكون هنالك خيطٌ إضافي مسؤولٌ عن معالجة الأحداث events ورسم مُكوِّنات الواجهة على الشاشة؛ وفي حالة اِستخدَام مكتبة <a href="https://academy.hsoub.com/programming/java/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A8%D8%B3%D9%8A%D8%B7-r1145/" rel="">جافا إف إكس JavaFX</a>، يكون ذلك الخيط هو خيط التطبيق، ويكون مسؤولًا عن إنجاز كل الأمور المتعلّقة بمعالجة الأحداث ورسم المكونات على الشاشة.
</p>

<p>
	تُعدّ البرمجة المتوازية أصعب نوعًا ما من البرمجة أحادية الخيط؛ فعند وجود عدة خيوطٍ تَعمَل معًا لحل مشكلةٍ معينة، قد تَنشَأ أنواعٌ جديدة من الأخطاء، ولذلك تُعدّ التقنيات المُستخدَمة لكتابة البرامج كتابة صحيحة ومتينة أكثر أهميةً بالبرمجة المتوازية منها بالبرمجة العادية. تُوفِّر جافا لحسن الحظ واجهة برمجة تطبيقات للخيوط threads <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> تُسِّهل كثيرًا من استخدام الخيوط على نحوٍ معقول، كما أنها تحتوي على الكثير من الأصناف القياسية التي تُغلِّف الأجزاء الأكثر تعقيدًا وتخفيها بالكامل. يُمكِننا إنجاز أمورٍ كثيرة باستخدام الخيوط دون أن نتعلم أي شيءٍ عن تقنياتها منخفضة المستوى.
</p>

<h2>
	إنشاء الخيوط وتشغيلها
</h2>

<p>
	يُمثَّل الخيط <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">بلغة جافا</a> بواسطة كائنٍ ينتمي إلى الصنف <code>java.lang.Thread</code>، أو إلى أيٍّ من أصنافه الفرعية؛ حيث يُنشَأ هذا الكائن بغرض تنفيذ تابعٍ method -يُمثِّل المهمة المُفترَض للخيط تنفيذها- لمرةٍ واحدةٍ فقط، أي يُنفَّذ ذلك التابع داخل خيط التحكم الخاص به، والذي يُمكِنه أن يَعمَل بالتوازي مع خيوطٍ أخرى. يتوقف الخيط عن العمل بعد الانتهاء من تنفيذ التابع طبيعيًا أو نتيجةً لحدوث استثناءٍ exception لم يُلتقَط، وعندها لا تتوفَّر أي طريقةٍ لإعادة تشغيله أو حتى لاستخدام الكائن المُمثِّل لذلك الخيط لإنشاء واحدٍ جديد.
</p>

<p>
	تتوفَّر طريقتان لبرمجة خيط؛ حيث تتمثّل الأولى بإنشاء صنفٍ فرعي من الصنف <code>Thread</code> يحتوي على تعريفٍ للتابع <code>public void run()‎</code>، ويكون هذا التابع مسؤولًا عن تعريف المُهمة التي سيُنفِّذها الخيط؛ فهو يَعمَل بمجرد بدء تشغيل الخيط. تُعرِّف الشيفرة التالية على سبيل المثال صنفًا بسيطًا لخيطٍ لا يَفعَل أكثر من مجرد طباعة رسالةٍ إلى الخرج القياسي standard output:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_13" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">NamedThread</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اسم الخيط</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">NamedThread</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// يضبُط الباني اسم الخيط</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// تُرسِل رسالة إلى الخرج القياسي</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Greetings from thread '"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> name </span><span class="pun">+</span><span class="pln"> </span><span class="str">"'!"</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يجب أن نُنشِئ كائنًا ينتمي إلى الصنف <code>NamedThread</code> لنتمكَّن من اِستخدامه. ألقِ نظرةً على ما يلي، على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_15" style="">
<span class="typ">NamedThread</span><span class="pln"> greetings </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">NamedThread</span><span class="pun">(</span><span class="str">"Fred"</span><span class="pun">);</span></pre>

<p>
	لا يؤدي إنشاء ذلك الكائن إلى بدء تشغيل الخيط، أو تنفيذ تابعه <code>run()‎</code> تلقائيًا، وإنما يجب استدعاء التابع <code>start()‎</code> المُعرَّف بالكائن. يُمكِننا مثلًا كتابة التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_17" style="">
<span class="pln">greetings</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span></pre>

<p>
	يُنشِئ التابع <code>start()‎</code> خيط تحكمٍ جديد مسؤولٍ عن تنفيذ التابع <code>run()‎</code> المُعرَّف بالكائن، حيث يعمل هذا الخيط الجديد على التوازي إلى جانب الخيط الذي استدعينا به التابع <code>start()‎</code>، وكذلك إلى جانب أي خيوطٍ أخرى موجودةٍ مسبقًا. ينتهي التابع <code>start()‎</code> من العمل ويُعيد قيمته بمجرد تشغيله للخيط الجديد دون أن ينتظر انتهاء الخيط من العمل؛ وهذا يَعنِي أن شيفرة التابع <code>run()‎</code> المُعرَّف بكائن الخيط تُنفَّذ بنفس الوقت الذي تُنفَّذ خلاله التعليمات التالية لتعليمة استدعاء التابع <code>start()‎</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_19" style="">
<span class="typ">NamedThread</span><span class="pln"> greetings </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">NamedThread</span><span class="pun">(</span><span class="str">"Fred"</span><span class="pun">);</span><span class="pln">
greetings</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
</span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Thread has been started"</span><span class="pun">);</span></pre>

<p>
	يوجد بعد تنفيذ التعليمة <code>greetings.start()‎</code> خيطان؛ حيث يَطبَع الأول جملة "Thread has been started"؛ بينما يريد الآخر طباعة جملة "!`Greetings from thread 'Fred". قد يختلف ترتيب طباعة الجملتين كلما شغَّلت البرنامج، حيث يَعمَل الخيطان بنفس الوقت، ويحاول كلٌ منهما الوصول إلى الخرج القياسي لطباعة الرسالة الخاصة به. وبالتالي، سيطبع الخيط الذي يتمكَّن من الوصول إلى الخرج القياسي أولًا، رسالته أولًا.
</p>

<p>
	يختلف ذلك عن البرامج العادية أحادية الخيط، حيث تُنفَّذ التعليمات بترتيبٍ مُحدَّد ومتوقَّع من البداية إلى النهاية؛ بينما هناك دائمًا عدم تحديد indeterminacy في البرامج متعددة الخيوط، فلا يكون الترتيب معروفًا أو محددًا، ولذلك لا يُمكِننا التأكُّد أبدًا من الترتيب الذي ستُنفَّذ على أساسه التعليمات، وهذا يَجعَل البرمجة المتوازية parallel programming صعبةً نوعًا ما.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: يختلف استدعاء التابع <code>greetings.start()‎</code> تمامًا عن استدعاء التابع <code>greetings.run()‎</code>؛ حيث يؤدي استدعاء <code>greetings.run()‎</code> إلى تنفيذ <code>run()‎</code> بنفس الخيط بدلًا من إنشاء خيطٍ جديد، أي تُنفَّذ شيفرته بالكامل قبل انتقال الحاسوب إلى التعليمة التالية لتعليمة استدعاء <code>greetings.run()‎</code>؛ أي لا يكون هناك توازٍ أو عدم تحديد.
		</p>
	</div>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="92254" href="https://academy.hsoub.com/uploads/monthly_2022_02/001Threads_Vs_Subroutines.png.bf169749c3fcebc88cb6d3720a7ac52d.png" rel=""><img alt="001Threads_Vs_Subroutines.png" class="ipsImage ipsImage_thumbnailed" data-fileid="92254" data-unique="3r429lzml" src="https://academy.hsoub.com/uploads/monthly_2022_02/001Threads_Vs_Subroutines.png.bf169749c3fcebc88cb6d3720a7ac52d.png"></a>
</p>

<p>
	افترضنا حتى الآن احتواء الحاسوب الذي نُشغِّل عليه البرنامج على أكثر من وحدة معالجةٍ واحدة، ويسمح هذا بتنفيذ كُلٍ من الخيط الأصلي والخيط الجديد بنفس الوقت حرفيًا. ومع ذلك، من الممكن حتى إنشاء عدة خيوطٍ بحاسوبٍ مكوَّنٍ من وحدة معالجة واحدة، ومن الممكن إنشاء خيوطٍ يتجاوز عددها عدد معالجات الحاسوب عمومًا. في تلك الحالة، يتنافس الخيطان على زمن المعالج، ويَظِل هناك نوعٌ من عدم التحديد؛ لأن المعالج يُمكِنه الانتقال من تنفيذ خيط لآخر بطريقةٍ غير متوقعة. أما بالنسبة للمبرمج، لا تختلف البرمجة بحاسوبٍ أحادي المعالج عنها بحاسوبٍ متعدد المعالجات ولهذا، سنتجاهل ذلك التمييز.
</p>

<p>
	كنا قد ذكرنا أن هناك طريقتين لبرمجة خيط؛ حيث كانت الطريقة الأولى بتعريف صنفٍ فرعي من الصنف <code>Thread</code>. ننتقل الآن إلى الطريقة الثانية، وهي بتعريف صنفٍ يُنفِّذ الواجهة <code>java.lang.Runnable</code>؛ حيث تُعرِّف تلك الواجهة interface تابعًا وحيدًا، هو <code>public void run()‎</code>. يُمكِننا إنشاء خيطٍ من النوع <code>Thread</code> مهمته هي تنفيذ التابع <code>run()‎</code> المُعرَّف بالواجهة، بمجرد حصولنا على كائنٍ منفِّذ لتلك الواجهة.
</p>

<p>
	يحتوي الصنف <code>Thread</code> على بانٍ constructor يَستقبِل كائنًا منفِّذًا للواجهة <code>Runnable</code> على أنه معاملٌ parameter. عندما نُمرِّر ذلك الكائن للباني، يَستدعِي تابع الخيط <code>run()‎</code> التابع <code>run()‎</code> المُعرَّف بالواجهة <code>Runnable</code>؛ وعندما نَستدعِي تابع الخيط <code>start()‎</code>، فإنه يُنِشئ خيط تحكمٍ جديد يكون مسؤولًا عن تنفيذ التابع <code>run()‎</code> المُعرَّف بالواجهة <code>Runnable</code>. يُمكِننا مثلًا تعريف الصنف التالي بدلًا من إنشاء الصنف <code>NamedThread</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_22" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">NamedRunnable</span><span class="pln"> implements </span><span class="typ">Runnable</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">  </span><span class="com">// الاسم</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">NamedRunnable</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// يَضبُط الباني اسم الكائن</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// يُرسِل رسالةً إلى الخرج القياسي</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Greetings from runnable '"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> name </span><span class="pun">+</span><span class="str">"'!"</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنُنشِئ الآن كائنًا ينتمي إلى الصنف <code>NamedRunnable</code> المُعرَّف بالأعلى، ونَستخدِمه لإنشاء كائن من النوع <code>Thread</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_24" style="">
<span class="typ">NamedRunnable</span><span class="pln"> greetings </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">NamedRunnable</span><span class="pun">(</span><span class="str">"Fred"</span><span class="pun">);</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> greetingsThread </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">(</span><span class="pln">greetings</span><span class="pun">);</span><span class="pln">
greetingsThread</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span></pre>

<p>
	تتميِّز تلك الطريقة عن الأولى في إمكانية أي كائنٍ من تنفيذ الواجهة <code>Runnable</code> وتعريف التابع <code>run()‎</code>، والذي يُمكِن تنفيذه بعد ذلك بخيطٍ منفصل. بالإضافة إلى ذلك، يستطيع التابع <code>run()‎</code> الوصول إلى أي شيءٍ مُعرَّفٍ بالصنف بما في ذلك توابعه ومتغيراته الخاصة private. في المقابل، لا تُعدّ تلك الطريقة <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-r1375/" rel="">كائنية التوجه object-oriented</a> تمامًا؛ فهي تخالف المبدأ الذي ينصّ على ضرورة أن يكون لكل كائنٍ مسؤوليةً وحيدةً محددةً بوضوح. لذلك، يكون من الأفضل أن نُعرِّف الخيط باستخدام صنف متداخل nested فرعي من الصنف <code>Thread</code>، بدلًا من إنشاء كائنٍ عشوائي من النوع <code>Runnable</code> فقط لنَستخدِمه مثل خيط. انظر مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D8%AA%D8%AF%D8%A7%D8%AE%D9%84%D8%A9-nested-classes-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1115/" rel="">الأصناف المتداخلة Nested Classes في جافا</a>.
</p>

<p>
	أخيرًا، لاحِظ أن الواجهة <code>Runnable</code> هي واجهة نوع دالة functional interface، أي يُمكِن تمريرها مثل تعبير لامدا lambda expression. يَعنِي ذلك أن بإمكان باني الصنف <code>Thread</code> استقبال تعبير لامدا على أنه معامل. ألقِ نظرةً على المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_31" style="">
<span class="typ">Thread</span><span class="pln"> greetingsFromFred </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">(</span><span class="pln"> 
    </span><span class="pun">()</span><span class="pln"> </span><span class="pun">-&gt;</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Greetings from Fred!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span><span class="pln">
greetingsFromFred</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span></pre>

<p>
	سنفحص الآن المثال التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest1.java" rel="external nofollow">ThreadTest1.java</a>، لنفهم طريقة تنفيذ الخيوط المتعددة على التوازي؛ حيث سيُنشِئ هذا البرنامج عدة خيوط، بحيث ينفِّذ كل خيطٍ منها نفس المهمة تمامًا. ستكون المهمة هي عدُّ الأعداد الصحيحة الأوليّة الأقل من 5000000. ليس هناك غرضٌ محددٌ من اختيار تلك المهمة بالتحديد، فكل ما يَهُمّ هنا هو أن تستغرق المهمة وقتًا طويلًا بعض الشيء. لاحِظ أيضًا أن هذا البرنامج غير واقعي، فمن الحماقة إنشاء عدة خيوط لتنفيذ الأمر نفسه. لاحِظ أيضًا عدم عمل التابع المسؤول عن العدّ بكفاءة عالية. لن يستغرق البرنامج أكثر من عدّة ثوانٍ على أي حاسوبٍ عصري. تُعرِّف الشيفرة التالية صنفًا متداخلًا ساكنًا static nested class لتمثيل الخيوط المسؤولة عن تنفيذ المهمة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_33" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pln"> extends </span><span class="typ">Thread</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">int</span><span class="pln"> id</span><span class="pun">;</span><span class="pln">  </span><span class="com">// مُعرِّف هوية لهذا الخيط </span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> id</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">long</span><span class="pln"> startTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">();</span><span class="pln">
      </span><span class="typ">int</span><span class="pln"> count </span><span class="pun">=</span><span class="pln"> countPrimes</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="lit">5000000</span><span class="pun">);</span><span class="pln"> </span><span class="com">// عدّ الأعداد الأولية</span><span class="pln">
      </span><span class="kwd">long</span><span class="pln"> elapsedTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> startTime</span><span class="pun">;</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Thread "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> id </span><span class="pun">+</span><span class="pln"> </span><span class="str">" counted "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> 
            count </span><span class="pun">+</span><span class="pln"> </span><span class="str">" primes in "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="pln">elapsedTime</span><span class="pun">/</span><span class="lit">1000.0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" seconds."</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	[1] عند تشغيل خيطٍ ينتمي إلى هذا الصنف، فإنه يَعُدّ عدد الأعداد الأولية الواقعة بنطاقٍ يتراوح من 2 إلى 5000000. سيَطبَع النتيجة إلى الخرج القياسي، مع رقم مُعرِّف الهوية الخاص به، وكذلك الزمن المُنقضِي منذ لحظة بدء المعالجة وحتى نهايتها.
</p>

<p>
	سيطلب البرنامج <code>main()‎</code> المُعرَّف فيما يلي من المُستخدِم إدخال عدد الخيوط المطلوب تشغيلها، ثم سيُنشِئ تلك الخيوط ويُشغِّلها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_35" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">int</span><span class="pln"> numberOfThreads </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">numberOfThreads </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> numberOfThreads </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">25</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"How many threads do you want to use  (1 to 25) ?  "</span><span class="pun">);</span><span class="pln">
      numberOfThreads </span><span class="pun">=</span><span class="pln"> </span><span class="typ">TextIO</span><span class="pun">.</span><span class="pln">getlnInt</span><span class="pun">();</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">numberOfThreads </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> numberOfThreads </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">25</span><span class="pun">)</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Please enter a number between 1 and 25 !"</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"\nCreating "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> numberOfThreads 
                                           </span><span class="pun">+</span><span class="pln"> </span><span class="str">" prime-counting threads..."</span><span class="pun">);</span><span class="pln">
   </span><span class="typ">CountPrimesThread</span><span class="pun">[]</span><span class="pln"> worker </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pun">[</span><span class="pln">numberOfThreads</span><span class="pun">];</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> numberOfThreads</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln">
      worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pun">(</span><span class="pln"> i </span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> numberOfThreads</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln">
      worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Threads have been created and started."</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ربما من الأفضل أن تُصرِّف compile البرنامج وتُشغِّله.
</p>

<p>
	عند تشغيل البرنامج باستخدام خيطٍ واحدٍ على حاسوبٍ قديمٍ نوعًا ما، يستغرق الحاسوب حوالي "6.251 ثانية" لإجراء العملية؛ وعند تشغيله باستخدام ثمانية خيوط، يكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_37" style="">
<span class="typ">Creating</span><span class="pln"> </span><span class="lit">8</span><span class="pln"> prime</span><span class="pun">-</span><span class="pln">counting threads</span><span class="pun">...</span><span class="pln">
</span><span class="typ">Threads</span><span class="pln"> have been created and started</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.264</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.569</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.567</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.569</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">7</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.562</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.565</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.569</span><span class="pln"> seconds</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Thread</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> counted </span><span class="lit">348513</span><span class="pln"> primes in </span><span class="lit">12.563</span><span class="pln"> seconds</span><span class="pun">.</span></pre>

<p>
	يَطبَع الحاسوب السطر الثاني تلقائيًا بعد السطر الأول، ويكون البرنامج <code>main()‎</code> في تلك اللحظة قد انتهى، بينما تستمر الثمانية خيوط الأخرى بالعمل. بعد فترةٍ تَصِل إلى "12.5 ثانية"، تكتمل جميع الخيوط الثمانية بنفس الوقت تقريبًا. لا يكون ترتيب انتهاء الخيوط من العمل هو نفسه ترتيب بدء تشغيلها، فالترتيب غير حتمي؛ أي إذا شغَّلنا البرنامج مرةً أخرى، فلربما سيختلف ذلك الترتيب.
</p>

<p>
	نظرًا لاحتواء الحاسوب على أربعة معالجات، استغرقت الثمانية خيوط عند تشغيلها عليه ضعف الزمن الذي استغرقه خيطٌ واحدٌ تقريبًا؛ فعند تشغيل ثمانية خيوط على أربعة معالجات (أي نصف معالج لكل خيط)، كان كل خيطٍ منها نَشطًا فعليًا لمدةٍ تصل إلى نصف ذلك الزمن فقط، ولهذا استغرقت ضعف الوقت لإنهاء نفس المهمة. بالمثل، إذا احتوى الحاسوب على معالجٍ واحدٍ فقط، فستستغرق الخيوط الثمان زمنًا يَصِل إلى ثمانية أضعاف الزمن الذي يستغرقه الخيط الواحد؛ وإذا احتوى الحاسوب على ثمانية معالجات أو أكثر، فلربما لن تستغرق الخيوط الثمان زمنًا أكبر مما يستغرقه الخيط الواحد. ومع ذلك، قد يكون التزايد الفعلي في السرعة أصغر قليلًا مما أشرنا إليه هنا نتيجةً لبعض التعقيدات، ويكون التزايد الفعلي مُحددًا في الحواسيب متعددة المعالجات. والآن حان دورك، ماذا يحدث عندما تُشغِّل البرنامج على حاسوبك الشخصي؟ كم عدد المعالجات الموجودة بحاسوبك؟
</p>

<p>
	عندما يكون هناك خيوطٌ أكثر من عدد المعالجات المتاحة، يُقسِّم الحاسوب قدرته المعالجية على الخيوط المُشغَّلة بالتبديل بينها بسرعة. يعني ذلك تشغيل كل معالجٍ خيطًا واحدًا لفترة، ثم الانتقال إلى خيطٍ آخر لتشغيله لفترة، ثم ينتقل لغيره، وهكذا. يُطلق على تلك التنقلات اسم <strong>تبديلات السياق context switches</strong>، والتي تحدث بمعدلٍ يصل إلى 100 مرة أو أكثر بالثانية الواحدة. يستطيع الحاسوب بتلك الطريقة إحراز بعض التقدم بجميع المهمات المطلوبة، ويظن المُستخدِم أنها تُنفَّذ جميعًا بنفس الوقت. ولهذا السبب، انتهت جميع الخيوط التي كان مطلوبًا منها نفس حجم العمل بنفس الوقت تقريبًا في المثال التوضيحي السابق. خلاصة القول أنه ولأي فترةٍ زمنيةٍ أكبر من جزءٍ من الثانية، فسيُقسَّم زمن الحاسوب بالتساوي تقريبًا على جميع الخيوط.
</p>

<h2>
	العمليات على الخيوط
</h2>

<p>
	ستجد غالبية <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-stream-api-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1433/" rel="">واجهة برمجة تطبيقات جافا</a> للخيوط مُعرَّفةً بالصنف <code>Thread</code>، ومع ذلك سنبدأ بتابعٍ متعلقٍ بالخيوط ومعرَّفٍ بالصنف <code>Runtime</code>؛ حيث يَسمَح ذلك الصنف لبرامج جافا بالوصول إلى بعض المعلومات عن البيئة التي يعمل عليها البرنامج. عند إجراء برمجةٍ على التوازي بغرض توزيع العمل على أكثر من معالجٍ واحد، فلربما يكون من المهم أن نعرف عدد المعالجات المتاحة أولًا، فقد تُنشِئ خيطًا واحدًا لكل معالجٍ مثلًا. تستطيع معرفة عدد المعالجات باستدعاء الدالة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_40" style="">
<span class="typ">Runtime</span><span class="pun">.</span><span class="pln">getRuntime</span><span class="pun">().</span><span class="pln">availableProcessors</span><span class="pun">()</span></pre>

<p>
	تُعيد تلك الدالة قيمةً من النوع <code>int</code> تُمثِّل عدد المعالجات المتاحة بآلة جافا الافتراضية Java Virtual Machine. قد تكون تلك القيمة في بعض الحالات أقل من عدد المعالجات الفعلية المتاحة بالحاسوب.
</p>

<p>
	يحتوي أي كائنٍ من النوع <code>Thread</code> على الكثير من التوابع المفيدة المتعلِّقة بالعمل مع الخيوط؛ حيث يُعدّ التابع <code>start()‎</code> الذي نُوقِش بالأعلى واحدًا من أهم تلك التوابع.
</p>

<p>
	بمجرد بدء الخيط، فإنه يَظَل مُشغَّلًا إلى حين انتهاء تابعه <code>run()‎</code> من العمل. من المفيد في بعض الأحيان أن يعرف خيط معين فيما إذا كان خيطٌ آخر قد انتهى أم لا؛ فإذا كان <code>thrd</code> كائنًا من النوع <code>Thread</code>، ستفحص الدالة <code>thrd.isAlive()‎</code> فيما إذا <code>thrd</code> قد انتهى أم لا. يُعدّ الخيط "نشطًا alive" منذ لحظة تشغيله إلى لحظة انتهائه، ويُعدّ "ميتًا dead" بعد انتهائه. تُستخدَم نفس تلك الاستعارة عندما نُشير إلى "إيقاف" أو "إلغاء" الخيط. تذكَّر أنه من غير الممكن إعادة تشغيل أي خيطٍ بعد انتهائه.
</p>

<p>
	يؤدي استدعاء التابع الساكن <code>Thread.sleep(milliseconds)‎</code> إلى "سُبات sleep" الخيط المُستدعِي لفترةٍ مساويةٍ للزمن المُمرَّر بوحدة الميللي ثانية. يُعدّ الخيط النائم sleep نشطًا، ولكنه غير مُشغَّل، ويستطيع الحاسوب تنفيذ أي خيوطٍ أو برامجٍ أخرى أثناء توقُّف ذلك الخيط. يُمكِننا استخدام التابع <code>Thread.sleep()‎</code> لإيقاف تنفيذ خيطٍ معين مؤقتًا. بإمكان التابع <code>sleep()‎</code> التبليغ عن استثناء من النوع <code>InterruptedException</code>، والذي يُعدّ من الاستثناءات المُتحقَّق منها checked exception، أي لا بُدّ من معالجته؛ ويَعنِي ذلك عمليًا ضرورة استدعاء التابع <code>sleep()‎</code> داخل تعليمة <code>try..catch</code> لالتقاط أي استثناءات محتملةٍ من النوع <code>InterruptedException</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_42" style="">
<span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="pln">lengthOfPause</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يستطيع خيطٌ معينٌ مقاطعة Interrupt خيطٍ آخر نائم أو مُتوقِّف لأسبابٍ أخرى بهدف إيقاظه. إذا كان <code>thrd</code> كائنًا من النوع <code>Thread</code>، فسيؤدي استدعاء التابع <code>thrd.interrupt()‎</code> إلى مقاطعته. يُمكِننا الاستعانة بذلك التابع إذا كان من الضروري إرسال إشارةٍ معينة من خيطٍ لآخر. عندما يلتقط أي خيطٍ استثناءًا من النوع <code>InterruptedException</code>، فإنه يُدرك أن خيطًا آخر قد قاطعه. بالإضافة إلى ذلك، يستطيع الخيط استدعاء التابع الساكن <code>Thread.interrupted()‎</code> بأي مكانٍ خارج عبارة <code>catch</code> ليَفحَص فيما إذا كان قد قُوطعَ بواسطة خيطٍ آخر.
</p>

<p>
	بمجرد استدعاء ذلك التابع، تنمحي حالة المقاطعة من الخيط؛ أي يستطيع الخيط قراءة حالة المقاطعة لمرةٍ واحدةٍ فقط، وهو ما قد تراه غريبًا بعض الشيء. بالنسبة للبرامج الخاصة بك، فلن يُقاطع أي خيط خيطًا آخرًا إلا إذا برمجته ليفعل ذلك بنفسك؛ لذلك لا تحتاج غالبًا لفعل أي شيء فيما هو متعلِّق بالاستثناء <code>InterruptedException</code> أكثر من مجرد التقاطه.
</p>

<p>
	يكون من الضروري في بعض الأحيان لخيطٍ معين الانتظار إلى حين انتهاء خيطٍ آخر من العمل. يُمكِننا فعل ذلك باستخدام التابع <code>join()‎</code> المُعرَّف بالصنف <code>Thread</code>. بفرض أن <code>thrd</code> كائنٌ من النوع <code>Thread</code>، يُمكِن لأي خيطٍ آخر استدعاء <code>thrd.join()‎</code>؛ مما يَعنِي أنه سيدخل في حالة سُبات sleep إلى حين انتهاء <code>thrd</code>. في حالة كان <code>thrd</code> ميتًا بالفعل عند استدعاء <code>thrd.join()‎</code>، لا يكون لها أي تأثير. بإمكان التابع <code>join()‎</code> التبليغ عن استثناءٍ من النوع <code>InterruptedException</code>، والذي يجب مُعالجته كما ذكرنا بالأعلى. على سبيل المثال، تُشغِّل الشيفرة التالية عدة خيوط، وتنتظر إلى حين انتهائها جميعًا من العمل، ثم تَطبَع الزمن المُستغرَق:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_44" style="">
<span class="typ">CountPrimesThread</span><span class="pun">[]</span><span class="pln"> worker </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pun">[</span><span class="pln">numberOfThreads</span><span class="pun">];</span><span class="pln">
</span><span class="kwd">long</span><span class="pln"> startTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> numberOfThreads</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CountPrimesThread</span><span class="pun">();</span><span class="pln">
   worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> numberOfThreads</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">join</span><span class="pun">();</span><span class="pln">  </span><span class="com">// انتظر إلى أن ينتهي إذا لم يكن قد انتهى بالفعل</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">// بالوصول إلى تلك اللحظة، تكون جميع الخيوط العاملة قد انتهت</span><span class="pln">
</span><span class="kwd">long</span><span class="pln"> elapsedTime </span><span class="pun">=</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">currentTimeMillis</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> startTime</span><span class="pun">;</span><span class="pln">
</span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Total elapsed time: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="pln">elapsedTime</span><span class="pun">/</span><span class="lit">1000.0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" seconds"</span><span class="pun">);</span></pre>

<p>
	ربما لاحظت أن تلك الشيفرة تفترض عدم حدوث استثناءاتٍ من النوع <code>InterruptedException</code>. إذا كانت تلك الاستثناءات محتملةً بالبيئة المُشغَّل عليها البرنامج، ينبغي استخدام الشيفرة التالية للتأكُّد تمامًا من أن الخيط الموجود بالكائن <code>worker[‎i]</code><code>‎</code> قد انتهى أم لا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_46" style="">
<span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">isAlive</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      worker</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">join</span><span class="pun">();</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستقبل نسخةٌ أخرى من التابع <code>join()‎</code> معاملًا من النوع العددي الصحيح، وهو يُمثِّل الحد الأقصى من الزمن المسموح بانتظاره بوحدة الميللي ثانية، حيث ينتظر الاستدعاء <code>thrd.join(m)‎</code> إلى أن ينتهي الخيط <code>thrd</code> من العمل، أو إلى أن يمر <code>m</code> ميللي ثانية، أو إلى أن يُقاطَع الخيط المُنتظِر. يُمكِننا استخدام ذلك التابع للسماح للخيط المُنتظِر بإنجاز مهمةٍ ما أثناء انتظاره للخيط الآخر. على سبيل المثال، تُشغِّل الشيفرة التالية خيطًا اسمه <code>thrd</code>، ثم تَطبَع الزمن المُنقضِي كل 2 ثانية طالما كان <code>thrd</code> مُشغَّلًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_48" style="">
<span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"Running the thread "</span><span class="pun">);</span><span class="pln">
thrd</span><span class="pun">.</span><span class="pln">start</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">thrd</span><span class="pun">.</span><span class="pln">isAlive</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      thrd</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="lit">2000</span><span class="pun">);</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"."</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InterruptedException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">" Done!"</span><span class="pun">);</span></pre>

<p>
	تتميز الخيوط بخاصيتين مفيدتين في بعض الأحيان، هما: الحالة الخفية daemon status والأولوية priority؛ حيث يُمكِن للخيط أن يُضبَط ليُصبِح خيطًا خفيًا باستدعاء التابع <code>thrd.setDaemon(true)‎</code> قبل بدء تشغيل الخيط. قد يُبلِّغ الاستدعاء عن استثناءٍِ من النوع <code>SecurityException</code> إذا لم يكن الخيط المُستدعِي قادرًا على تعديل خاصيات الخيط <code>thrd</code>. وفي تلك الحالة، يكون إنهاء آلة جافا الافتراضية ممكنًا بمجرد انتهاء جميع الخيوط الحيّة غير الخفية، أي لا يُعدّ وجود بعض الخيوط الحيّة الخفية كافيًا لإبقاء آلة جافا الافتراضية مشغَّلة. يُعدّ ذلك منطقيًا، فالخيوط الخفية بالنهاية موجودةٌ فقط لتوفير بعض الخدمات للخيوط غير الخفية، ونظرًا لعدم وجود أي خيوطٍ غير خفية أخرى، لا تُستدَعى تلك الخدمات التي توفِّرها الخيوط الخفية مجددًا، ولذلك يُمكِن إنهاء البرنامج أيضًا. لاحِظ أن استدعاء <code>System.exit()‎</code> ينهِي آلة جافا الافتراضية JVM إجباريًأ حتى في حالة وجود بعض الخيوط المُشغَّلة غير الخفية.
</p>

<p>
	تُعدّ أولوية الخيط خاصيةً أكثر أهمية، حيث يمتلك أي خيط عمومًا أولويةً مُمثَلةً باستخدام عددٍ صحيح، ويكون تشغيل الخيوط ذات الأولوية الأكبر مُفضّلًا على حساب تشغيل الخيوط ذات الأولوية الأقل. على سبيل المثال، يُمكِن للعمليات الموجودة بالخلفية، والتي تُشغَّل عندما لا يكون هناك عملٌ ضروريٌ بخيطٍ هام آخر، أن تُشغَّل بأولوية أقل. إذا كان <code>thrd</code> كائنًا من النوع <code>Thread</code>، يُعيد التابع <code>thrd.getPriority()‎</code> عددًا صحيحًا يُمثِّل أولوية الخيط <code>thrd</code>، بينما يَضبُط التابع <code>thrd.setPriority(p)‎</code> أولوية الخيط إلى العدد الصحيح المُخصَّص <code>p</code>.
</p>

<p>
	لا يُمكِن تخصيص أي أعدادٍ صحيحة عشوائية على أنها أولويةً لخيطٍ معين، وسيبلِّغ التابع <code>thrd.setPriority()‎</code> عن استثناءِ من النوع <code>llegalArgumentException</code>، إذا لم تَكن الأولوية المُخصَّصة بالنطاق المسموح به للخيط. يختلف نطاق الأعداد المسموح بها لقيم أولوية خيطٍ من حاسوبٍ لآخر، وتكون مُخصَّصةً عبر الثوابت <code>Thread.MIN_PRIORITY</code> و <code>Thread.MAX_PRIORITY</code>، ومع ذلك، يُمكِن تقييد أولوية خيطٍ معينٍ لتقع ضمن قيمٍ أقل من الثابت <code>Thread.MAX_PRIORITY</code>. يتوفَّر أيضًا الثابت <code>Thread.NORM_PRIORITY</code> الذي يُمثِّل القيمة الافتراضية لأولوية خيط. يُمكِننا استخدام التعليمة التالية لضبط الخيط <code>thrd</code>؛ بحيث يَعمَل بقيمة أولوية أقل من القيمة الافتراضية بقليل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_50" style="">
<span class="pln">thrd</span><span class="pun">.</span><span class="pln">setPriority</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Thread</span><span class="pun">.</span><span class="pln">NORM_PRIORITY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">);</span></pre>

<p>
	ملاحظة: قد يُبلِّغ التابع <code>thrd.setPriority()‎</code> عن استثناءِ من النوع <code>SecurityException</code> أيضًا إذا لم يَكن مسموحًا للخيط المُستدعِي بضَبْط أولوية الخيط <code>thrd</code> إلى القيمة المُمرَّرة.
</p>

<p>
	أخيرًا، يعيد التابع الساكن <code>Thread.currentThread()‎</code> الخيط الحالي؛ أي أنه يعيد الخيط المُستدِعي لنفس ذلك التابع، وبذلك يستطيع الخيط أن يحصُل على مرجع reference لذاته، وهو ما يُمكِّنه من تعديل خاصياته. يُمكِننا مثلًا تحديد أولوية الخيط الجاري تشغيله باستدعاء <code>Thread.currentThread().getPriority()‎</code>.
</p>

<h2>
	الإقصاء التشاركي Mutual Exclusion وتعليمة التزامن synchronized
</h2>

<p>
	من السهل برمجة عدة خيوطٍ لتنفيذ بعض المهمات المستقلة تمامًا. تَكْمُن الصعوبة الحقيقية عندما تضطّر الخيوط للتفاعل مع بعضها بطريقةٍ أو بأخرى. تُعدّ مشاركة الموارد resources واحدةً من طرق تفاعل الخيوط مع بعضها؛ فعندما يحتاج خيطان مثلًا للوصول إلى نفس المورد، مثل متغير أو نافذةٍ على الشاشة، لا بُدّ من التأكُّد من عدم استخدامهما لنفس المورد بنفس اللحظة؛ وإلا سيكون الموقف مشابهًا لما يلي: إذا كان لدينا مجموعةٌ من الطباخين يتشاركون استخدام كوب قياسٍ واحدٍ فقط. لنتخيل أن الطباخ A قد ملئ كوب القياس بالحليب، وقبل أن يتمكَّن من تفريغه بالصحن الخاص به، أمسك الطباخ B بكوب القياس المملوء بالحليب. لذلك، لا بُدّ من توفير طريقة للطباخ A تُمكِّنه من المطالبة بأحقيته وحده للوصول إلى الكوب أثناء تنفيذه للعمليتين: إضافة الحليب إلى الكوب وتفريغ الكوب بالصحن.
</p>

<p>
	ينطبق الأمر ذاته على الخيوط حتى أثناء إجرائها لعمليةٍ بسيطة مثل زيادة قيمة عدادٍ بمقدار الواحد. ألقِ نظرةً على التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_53" style="">
<span class="pln">count </span><span class="pun">=</span><span class="pln"> count </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span></pre>

<p>
	في الواقع، تتكوَّن التعليمة السابقة فعليًا من ثلاث عمليات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_55" style="">
<span class="com">// اقرأ قيمة العداد</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1.</span><span class="pln">  </span><span class="typ">Get</span><span class="pln"> the value of count
</span><span class="com">// زِد قيمة العداد بمقدار الواحد</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">2.</span><span class="pln">  </span><span class="typ">Add</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> to the value</span><span class="pun">.</span><span class="pln">
</span><span class="com">// خزِّن القيمة الجديدة بالعداد</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">3.</span><span class="pln">  </span><span class="typ">Store</span><span class="pln"> the </span><span class="kwd">new</span><span class="pln"> value in count</span></pre>

<p>
	لنفترض أنه لدينا مجموعةٌ من الخيوط تنفِّذ جميعها نفس الخطوات الثلاثة السابقة. تذكَّر أنه من الممكن تشغيل خيطين بنفس الوقت حتى في حالة وجود معالجٍ واحدٍ فقط؛ حيث يستطيع ذلك المعالج التبديل بين الخيوط الموجودة بأي لحظة. لنفترض الآن أنه وبينما كان خيطٌ معينٌ بين الخطوتين الثانية والثالثة، بدأ خيطٌ آخر بتنفيذ نفس مجموعة الخطوات. نظرًا لعدم تخزين الخيط الأول القيمة الجديدة داخل المُتغيّر <code>count</code> بعد، سيقرأ الخيط الآخر القيمة "القديمة" للمتغير <code>count</code>، وبالتالي سيزيد تلك القيمة بمقدار الواحد.
</p>

<p>
	بناءً على ذلك، يَحسِب الخيطان نفس القيمة الجديدة، وعند تنفيذهما للخطوة الثالثة، يخزِّن كلاهما تلك القيمة بالمتغير <code>count. بعد انتهاء الخيطين من العمل، تكون قيمة المتغير</code>count` قد ازدادت بمقدار 1 فقط بدلًا من 2. يُطلَق على هذا النوع من المشكلات اسم "حالة التسابق race condition"، والتي تَحدُث في حالة وجود خيطٍ معينٍ وسط عمليةٍ مكوَّنةٍ من عدة خطوات، ويُغيّر خيطٌ آخر قيمةً أو شرطًا يعتمد عليه الخيط الأول لإتمام العملية التي يُجريها؛ ويُقال أن الخيط الأول يكون في حالة "تسابق" لإكمال جميع الخطوات قبل أن يقاطعه خيط آخر.
</p>

<p>
	يُمكِن أن تقع حالة التسابق أيضًا بتعليمة <code>if</code> الشرطية. لنفحص التعليمة التالية التي تحاول تجنُّب وقوع خطأ القسمة على صفر:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_57" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> A </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   B </span><span class="pun">=</span><span class="pln"> C </span><span class="pun">/</span><span class="pln"> A</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنفترض أن تلك التعليمة مُشغَّلةٌ بخيطٍ معين، ولنفترض أن هنالك خيطٌ آخر أو عدة خيوطٍ أخرى تتشارك مع الخيط الأول المورد <code>A</code>. إذا لم نُوفِّر حمايةً ضد حالة التسابق بطريقةٍ ما، فبإمكان أيٍّ من تلك الخيوط تعديل قيمة <code>A</code> إلى الصفر في اللحظة الواقعة بين لحظة فحص الخيط الأول للشرط <code>A != 0</code>، ولحظة إجراءه لعملية القسمة؛ أي قد ينتهي به الحال بإجراء عملية القسمة على صفر على الرغم من أنه قد فحص للتو أن المتغير <code>A</code> لا يساوي الصفر.
</p>

<p>
	لحل مشكلة حالات التسابق، لا بُدّ من توفُّر طريقة تُمكِّن الخيط من الوصول وحده دون غيره إلى موردٍ تشاركي. في الواقع، لا يُعدّ ذلك أمرًا سهل التنفيذ، ولكن تُوفِّر جافا أسلوبًا عالي المستوى وسهل الاستخدام نسبيًا لتحقيق ذلك باستخدام التوابع المتزامنة synchronized، وتعليمة <code>synchronized</code>؛ حيث توفِّر تلك الطرائق حمايةً للموارد التي تتشاركها عدة خيوط، وذلك من خلال السماح لخيطٍ واحدٍ فقط بالوصول إلى المورد بكل مرة.
</p>

<p>
	يُوفِّر <a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1270/" rel="">التزامن بلغة جافا</a> ما يُعرف باسم "الإقصاء التشاركي mutual exclusion"، والذي يَعنِي ضمان تحقُّق الوصول الإقصائي لموردٍ معين إذا استخدمت جميع الخيوط التي تحتاج الوصول إلى ذلك المورد المزامنة. إذا طبقنا مبدأ المزامنة على مثال الطباخين، فإنه يَعنِي أن يترك الطباخ الأول ملاحظةً تقول "إنني استخدِم كوب القياس"، وهو ما يمنحه أحقيةً حصريّةً إقصائية في الوصول إلى الكوب، ولكن لن يتحقَّق ذلك إلا إذا اتفق جميع الطباخين على فحص الملاحظة قبل محاولة الإمساك بالكوب.
</p>

<p>
	نظرًا لأنه موضوع معقدٌ نوعًا ما، سنبدأ بمثالٍ بسيط. لنفترض أننا نريد تجنُّب حالة التسابق ممكنة الحدوث عند محاولة مجموعةٍ من الخيوط زيادة قيمة عدادٍ بمقدار الواحد. بدايةً، سنُعرِّف صنفًا يُمثِّل العداد، وسنَستخدِم التوابع المتزامنة ضمن ذلك الصنف. يُمكِننا الإعلان عن أن تابعًا معينًا هو من النوع المتزامن بإضافة الكلمة المحجوزة <code>synchronized</code> مثل مُعدِّلٍ بتعريف ذلك التابع على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_60" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ThreadSafeCounter</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

   </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> count </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">  </span><span class="com">// قيمة العداد</span><span class="pln">

   synchronized </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> increment</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      count </span><span class="pun">=</span><span class="pln"> count </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">

   synchronized </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> getValue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> count</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	إذا كان <code>tsc</code> من النوع <code>ThreadSafeCounter</code>، يُمكِن لأي خيطٍ استدعاء التابع <code>tsc.increment()‎</code> لزيادة قيمة العداد بمقدار 1 بطريقةٍ آمنةٍ تمامًا. نظرًا لأن التابع <code>tsc.increment()‎</code> متزامن، سيكون بإمكان خيطٍ واحدٍ فقط تنفيذه بالمرة الواحدة. يَعنِي ذلك أنه وبمجرد بدء خيطٍ معين بتنفيذ ذلك التابع، فسيُنهي ذلك الخيط تنفيذ التابع بالضرورة قبل أن يُسمَح لخيطٍ آخر بالوصول إلى <code>count</code>. وبالتالي لا يكون هناك أي احتماليةٍ لحدوث حالة تسابق.
</p>

<p>
	لاحِظ أن ما سبق مشروطٌ بحقيقة أن <code>count</code> مُعرَّف على أنه متغيرٌ خاص private، وبالتالي لا بُدّ أن تحدث أي محاولةٍ للوصول إليه عبر التوابع المتزامنة المُعرَّفة بالصنف؛ وإذا كان <code>count</code> مُعرَّفًا على أنه متغيرٌ عام، فبإمكان خيطٍ آخر اجتياز المزامنة بكتابة <code>tsc.count++‎</code> مثلًا، وستتغيّر في تلك الحالة قيمة المتغير <code>count</code> بينما ما يزال خيطٌ آخر يُنفِّذ عملية <code>tsc.increment()‎</code>؛ أي لا تضمَن عملية المزامنة بحد ذاتها تحقُّق الوصول الإقصائي للموارد في العموم، وإنما تضمَن تحقُّق "الإقصاء التشاركي" بين جميع الخيوط المتزامنة فقط.
</p>

<p>
	ومع ذلك، لا يمنع الصنف <code>ThreadSafeCounter</code> جميع حالات التسابق محتملة الحدوث عند استخدام مجموعة خيوطٍ لعداد. انظر تعليمة <code>if</code> التالية مثلًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_62" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> tsc</span><span class="pun">.</span><span class="pln">getValue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   doSomething</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتطلَّب التابع <code>doSomething()‎</code> أن تكون قيمة العداد مساويةً للعدد 10. قد تحدث حالة تسابق إذا زاد خيطٌ آخر قيمة العداد بين لحظتي اختبار الخيط الأول للشرط <code>tsc.getValue() == 10</code> وتنفيذه للتابع <code>doSomething()‎</code>. يحتاج الخيط الأول إذًا إلى وصولٍ إقصائي إلى العداد أثناء تنفيذه تعليمة <code>if</code> بالكامل؛ بينما تمنحه المزامنة بالصنف <code>ThreadSafeCounter</code> وصولًا إقصائيًا أثناء تحصيله لقيمة <code>tsc.getValue()‎</code> فقط. يُمكِننا حل تلك المشكلة بوضع تعليمة <code>if</code> داخل تعليمة <code>synchronized</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_64" style="">
<span class="pln">synchronized</span><span class="pun">(</span><span class="pln">tsc</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> tsc</span><span class="pun">.</span><span class="pln">getValue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">)</span><span class="pln">
      doSomething</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستقبل تعليمة <code>synchronized</code> كائنًا على أنه معاملٌ -كان <code>tsc</code> في المثال السابق-، وتُكتَب وفقًا لقواعد الصيغة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_66" style="">
<span class="pln">synchronized</span><span class="pun">(</span><span class="pln"> object </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   statements
</span><span class="pun">}</span></pre>

<p>
	يرتبط الإقصاء التشاركي بجافا دائمًا بكائنٍ معين، ويقال أن التزامن مبنيٌ على ذلك الكائن، حيث يُعدّ تزامن تعليمة <code>if</code> بالأعلى مثلًا مبنيًا على الكائن <code>tsc</code>؛ بينما يُعدّ تزامن توابع النسخ instance method المتزامنة، مثل تلك المُعرَّفة بالصنف <code>ThreadSafeCounter</code>، مبنيًا على الكائن المُتضمِّن لتابع النسخة. تتكافئ إضافة المُعدِّل <code>synchronized</code> إلى تعريف تابع نسخة مع كتابة متن التابع داخل تعليمة <code>synchronized</code> على الصيغة التالية <code>synchronized(this) {...}‎</code>. من الممكن أيضًا تعريف توابعٍ ساكنةٍ متزامنة، ويُعدّ تزامنها مبنيًا على الكائن الخاص الذي يُمثِّل الصنف المُتضمِّن للتابع الساكن.
</p>

<p>
	لا يُمكِن أن يتزامن خيطان بناءً على نفس الكائن بنفس الوقت، حيث تُعدّ العبارة السابقة القاعدة الحقيقية للمزامنة <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/" rel="">بلغة جافا</a>؛ وتَعنِي أنه لا يُمكِن لخيطين تنفيذ شيفرتين متزامنتين بناءً على نفس الكائن بنفس الوقت؛ بمعنى أنه إذا تزامن خيطٌ معينٌ بناءً على كائنٍ معين، وحاول خيطٌ آخر أن يتزامن بناءً على نفس الكائن، فسيضطّر الخيط الثاني للانتظار إلى حين انتهاء الخيط الأول. في الواقع، لا يعني ذلك أنه ليس بإمكانهما فقط تنفيذ نفس التابع المتزامن بنفس الوقت، وإنما ليس بإمكانهما أن يُنفِّذا تابعين مختلفين بنفس الوقت إذا كان تزامن هذين التابعين مبنيًا على نفس الكائن.
</p>

<p>
	تُنفِّذ جافا ذلك باستخدام "قفل المزامنة synchronization lock، حيث يملك كل كائنٍ قفلًا يُمكِن أن يحصُل عليه خيطٌ واحدٌ فقط خلال أي لحظة. عندما نستخدِم تعليمة <code>synchronized</code> أو نستدعي تابعًا متزامنًا، فلا بُدّ للخيط أن يحصل على قفل الكائن المبني عليه التزامن أولًا؛ فإذا كان القفل متاحًا، سيحصل الخيط عليه فورًا ويبدأ بتنفيذ الشيفرة المتزامنة، ثم يُحرِّره بمجرد انتهاءه من تنفيذها؛ أما إذا حاول الخيط A الحصول على قفلٍ قد حصل عليه خيطٌ آخر B، فلا بُدّ إذًا أن ينتظر الخيط A حتى يُحرِّر الخيط B القفل؛ أي يتوقَّف/ينام الخيط A، ولا يعود للعمل حتى يُصبِح القفل متاحًا.
</p>

<p>
	ذكرنا بالقسم اللامتغايرات من مقال <a href="https://academy.hsoub.com/programming/java/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1308/" rel="">كيفية كتابة برامج صحيحة باستخدام لغة جافا</a> أن التفكير بطريقة عمل اللا متباين invariants ستصبح أعقد كثيرًا عند استخدام الخيوط، حيث تَكُمن المشكلة في حالات التسابق race conditions. نريد للصنف <code>ThreadSafeCounter</code> أن يُحقِّق لا متباين الصنف الذي ينص على: "تُمثِّل قيمة <code>count</code> عدد مرات استدعاء <code>increment()‎</code>". يتحقَّق ذلك بدون مزامنة synchronization بالبرامج أحادية الخيط، ولكن تصبح المزامنة ضرورية بالبرامج متعددة الخيوط للتأكُّد من تحقُّق لا متباين الصنف.
</p>

<p>
	سنعود الآن إلى مسألة عدّ الأعداد الأولية بمثابة مثالٍ بسيطٍ على الموارد التشاركية shared resources، وبدلًا من أن تُنفِّذ جميع الخيوط نفس المهمة تمامًا، سننفِّذ معالجةً على التوازي أكثر واقعية. سيَعُدّ البرنامج الأعداد الأولية ضمن نطاقٍ معين من الأعداد الصحيحة، وسيفعل ذلك بتوزيع العمل على عدة خيوط؛ أي سيتعيّن على كل خيط عدّ الأعداد الأولية الموجودة ضمن جزءٍ معين من النطاق الكلي، ثم سيضطّر لإضافة القيمة التي حسبها إلى المجموع الكلي ضمن كامل النطاق. نظرًا لأن جميع الخيوط مضطرةٌ لإضافة عددٍ إلى المجموع الكلي، فلا بُدّ أن تتشارك جميعها مُتغيرًا يُمثِّل ذلك المجموع الكلي، وليكن اسمه هو <code>total</code>. إذا اِستخدَمت جميع الخيوط التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_69" style="">
<span class="pln">total </span><span class="pun">=</span><span class="pln"> total </span><span class="pun">+</span><span class="pln"> count</span><span class="pun">;</span></pre>

<p>
	فهناك احتماليةٌ ولو صغيرة أن يحاول خيطان تنفيذ التعليمة ذاتها بنفس الوقت، وستكون القيمة النهائية للمتغير <code>total</code> غير صحيحة. لذلك لا بُدّ أن يكون الوصول إلى <code>total</code> متزامنًا لمنع حالة التسابق race condition. سنَستخدِم ضمن هذا البرنامج تابعًا متزامنًا يَسمَح بزيادة قيمة <code>total</code> بمقدارٍ معين، بحيث يَستدعِي كل خيطٍ ذلك التابع لمرةٍ واحدة. سيُمثِّل ذلك التابع الطريقة الوحيدة لتعديل قيمة <code>total</code>. ألقِ نظرةً على شيفرة التابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_71" style="">
<span class="pln">synchronized </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> addToTotal</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> x</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   total </span><span class="pun">=</span><span class="pln"> total </span><span class="pun">+</span><span class="pln"> x</span><span class="pun">;</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">total </span><span class="pun">+</span><span class="pln"> </span><span class="str">" primes found so far."</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُمكِنك الإطلاع على شيفرة البرنامج من الملف <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest2.java" rel="external nofollow">ThreadTest2.java</a>، حيث يَعُدّ هذا البرنامج الأعداد الأولية الواقعة بنطاقٍ يتراوح من 3000001 إلى 6000000 (مجرد أعداد عشوائية). يُنشِئ البرنامج <code>main()‎</code> عدة خيوطٍ يتراوح عددها من "1" إلى "5"، ويُسنِد جزءًا من المهمة لكل خيط، ثم ينتظر إلى أن تنتهي جميع الخيوط من عملها باستخدام التابع <code>join()‎</code> المذكور بالأعلى، وأخيرًا يُبلّغ عن العدد الكلي للأعداد الأولية مصحوبًا بالزمن الذي استغرقه البرنامج لحساب ذلك العدد. لاحِظ أن اِستخدَام التابع <code>join()‎</code> ضروري؛ فلا معنى لطباعة عدد الأعداد الأولية قبل أن تنتهي جميع الخيوط. إذا شغَّلت البرنامج على حاسوبٍ متعدّد المعالجات، فسيستغرق البرنامج وقتًا أقل عند استخدام أكثر من مجرد خيطٍ واحد.
</p>

<p>
	تساعد المزامنة على منع حالات التسابق، ولكنها قد تتسبَّب بنوعٍ آخر من الأخطاء يُدعى "قفل ميت deadlock". يحدث هذا النوع من الأخطاء عندما ينتظر خيطٌ موردًا معينًا إلى الأبد دون أن يحصل عليه. إذا عدنا إلى مثال "المطبخ"، فلربما يحدث القفل الميت إذا أراد طبّاخان ساذجان قياس كوب حليب بنفس الوقت، حيث سيُمسِك الطباخ الأول بكوب القياس، بينما سيُمسِك الطباخ الآخر بالحليب. يحتاج الطباخ الأول إلى الحليب، ولكنه لا يستطيع العثور عليه لأنه بحوزة الطباخ الثاني. ومن الجهة الأخرى، يحتاج الطباخ الثاني إلى كوب القياس، ولكنه لا يستطيع العثور عليه لأنه بحوزة الطباخ الأول. وبذلك، لا يتمكَّن الطباخان من إكمال عملهما. يُعدّ ذلك بمثابة قفل ميت، وقد يحدث نفس الشيء ببرنامجٍ معين إذا كان هناك خيطين (الطباخين) مثلًا يريد كلاهما الحصول على قفلين على كائنين معينين (الحليب وكوب القياس) قبل أن يكملا عملهما. تقع الأقفال الميتة بسهولة إذا لم ننتبه بما يكفي لمنعها.
</p>

<h2>
	المتغيرات المتطايرة
</h2>

<p>
	تُعدّ المزامنة واحدةً ضمن عدة تقنيات تتحكَّم بالتواصل بين الخيوط، وسنتناول تقنياتٍ أخرى لاحقًا، أما الآن فسنُنهِي هذا القسم بمناقشة التقنيتين التاليتين: المتغيرات المتطايرة volatile variables والمتغيرات الذرية atomic variables.
</p>

<p>
	تتواصل الخيوط مع بعضها عمومًا من خلال تشارك عدة متغيرات والوصول إليها من خلال استخدام توابعٍ متزامنة أو تعليمة <code>synchronized</code>. ومع ذلك، تُعدّ عملية المزامنة مكلفةً حاسوبيًا، ولهذا ينبغي تجنُّب اِستخدَامها بكثرة، وقد يكون من المنطقي في بعض الأحيان للخيوط الإشارة إلى المتغيرات التشاركية دون مزامنة وصولها إلى تلك المتغيرات.
</p>

<p>
	من الجهة الأخرى، قد تنشأ مشكلةٌ صغيرة إذا كانت قيمة متغير تشاركي تُضبَط بخيطٍ وتُستخدَم بآخر. نظرًا للطريقة التي تُنفِّذ جافا الخيوط على أساسها، فلربما لا يرى الخيط الثاني القيمة الجديدة للمتغير على الفور؛ ويَعنِي ذلك أنه من الممكن لخيطٍ معين الاستمرار بقراءة القيمة "القديمة" لمتغيرٍ تشاركي لمدةٍ معينة بالرغم من أن قيمة ذلك المتغير قد عُدِّلت بخيطٍ آخر. يَحدث ذلك لأن جافا تسمح للخيوط بتخزين البيانات التشاركية مؤقتًا cache؛ أي يُمكِن لكل خيطٍ الاحتفاظ بنسخته المحلية من البيانات التشاركية، وبالتالي عندما يُعدِّل خيطٌ معينٌ قيمة متغيرٍ تشاركي، لا تُعدَّل النسخ المحلية بمخزِّنات الخيوط الأخرى المؤقتة على الفور، وقد تستمر تلك الخيوط بقراءة القيمة القديمة لفترةٍ قصيرةٍ على الأقل.
</p>

<p>
	في المقابل، يُعدّ استخدام مُتغيّر تشاركي داخل تابعٍ متزامن أو داخل تعليمة <code>synchronized</code> آمنًا شرط أن تكون جميع محاولات الوصول إلى ذلك المُتغيّر متزامنةً بناءً على نفس الكائن بجميع الحالات. بعبارة أخرى، إذا حاول خيطٌ معين الوصول إلى قيمة متغيرٍ داخل شيفرةٍ متزامنة، فإنه يَضمَن أن يرى أي تغييرات تجريها الخيوط الأخرى على ذلك المتغير شرط أن تكون تلك التعديلات قد أُجريت داخل شيفرةٍ متزامنةٍ بناءً على نفس الكائن.
</p>

<p>
	يُمكِننا أيضًا استخدام متغيرٍ تشاركي اِستخدَامًا آمنًا خارج شيفرةٍ متزامنة، ولكن لا بُدّ في تلك الحالة أن نُصرِّح عن كون المتغير متطايرًا volatile باستخدام كلمة <code>volatile</code>، حيث تُعدّ الكلمة المحجوزة <code>volatile</code> واحدةً من المُعدِّلات modifiers المُمكِن إضافتها إلى تعليمات التصريح عن المتغيرات العامة global variable على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_73" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">volatile</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> count</span><span class="pun">;</span></pre>

<p>
	إذا صرَّحنا عن متغيرٍ باستخدام كلمة <code>volatile</code>، فلن يتمكَّن أي خيطٍ من الاحتفاظ بنسخةٍ محليةٍ من ذلك المتغير ضمن مُخزِّنه المؤقت، وستضطر الخيوط بدلًا من ذلك من استخدام النسخة الرئيسية الأصلية للمتغير دائمًا، وستكون بالتالي التعديلات المُجراة على ذلك المتغير مرئيةً لجميع الخيوط فورًا. بناءً على ذلك، يصبح من الآمن للخيوط الإشارة إلى المتغيرات التشاركية المُصرَّح عنها بكلمة <code>volatile</code> حتى خارج الشيفرات المتزامنة. ومع ذلك، يُعدّ الوصول إلى المتغيرات المتطايرة أقل كفاءةً من الوصول إلى المتغيرات العادية غير المتطايرة، ولكنه على الأقل أكثر كفاءةً من استخدام المزامنة. تذكَّر مع ذلك أن استخدام المتغيرات المتطايرة لا يحمي ضد حالات التسابق التي قد تحدث عند زيادة قيمة المتغير مثلًا، فربما يُقاطِع خيطٌ آخر عملية الزيادة.
</p>

<p>
	عند تطبيق المُعدِّل <code>volatile</code> على متغيرٍ من نوع كائني، فإن ما يُصرَّح عنه ليكون متطايرًا هو المتغير ذاته فقط لا محتويات الكائن الذي يشير إليه المتغير، ولهذا يُستخدَم غالبًا المُعدِّل <code>volatile</code> مع المتغيرات من الأنواع البسيطة، مثل الأنواع الأساسية primitive types، أو الأنواع الثابتة غير المتغيرة immutable types مثل <code>String</code>.
</p>

<p>
	لنفحص الآن مثالًا على استخدام متغيرٍ متطايرٍ لإرسال إشارةٍ من خيط لآخر ليخبره بأن عليه الانتهاء terminate. يتشارك الخيطان المتغير التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_76" style="">
<span class="kwd">volatile</span><span class="pln"> boolean terminate </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span></pre>

<p>
	يفحص التابع <code>run()‎</code> الخاص بالخيط الثاني قيمة <code>terminate</code> باستمرار، وينتهي عندما تصبح قيمته مساويةً القيمة <code>true</code>. ألقِ نظرةً على الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_78" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> run</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> terminate </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">  </span><span class="com">// Do some work.</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيستمر هذا الخيط بالعمل إلى أن يضبط خيطٌ آخر قيمة <code>terminate</code> إلى <code>true</code>. تُعدّ الشيفرة السابقة الطريقة الوحيدة للسماح لخيطٍ بغلْق خيطٍ آخر بطريقةٍ نظيفة.
</p>

<p>
	قد تتساءل عن سبب استعانة الخيوط ببياناتٍ محليةٍ مؤقتة من الأساس، خاصةً وأنها تُعقّد الأمور بصورةٍ غير ضرورية. في الواقع، يُسمَح للخيوط بالتخزين المؤقت نتيجةً لبنية الحواسيب متعددة المعالجات، حيث يملك كل معالجٍ ذاكرةً محليةً متصلةً به مباشرةً، وتُخزَّن الذاكرة المحلية المؤقتة للخيط بالذاكرة المحلية للمعالج المُشغَّل عليه الخيط. يُعدّ الوصول إلى تلك الذاكرة المحلية أسرع بكثير من الوصول إلى الذاكرة الرئيسية التي تتشاركها جميع المعالجات، ولذلك يُعدّ اِستخدَام الخيط لنسخةٍ محليةٍ من المتغيرات التشاركية أكثر كفاءةً من استخدام نسخةٍ رئيسيةٍ مخزَّنةٍ بالذاكرة الرئيسية.
</p>

<h2>
	المتغيرات الذرية
</h2>

<p>
	تكْمُن مشكلة <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r662/" rel="">البرمجة</a> على التوازي بتعليمةٍ مثل <code>count = count + 1</code> في أنها تستغرق عدة خطوات لتنفيذ التعليمة، وتكون التعليمة قد نُفذَّت على النحو الصحيح فقط إذا اكتملت الخطوات دون حدوث أي تقاطعٍ مع خيوطٍ أخرى.
</p>

<p>
	تُعدّ العمليات الذرية atomic operation بمثابة شيءٍ لا يمكن مقاطعته؛ فإما يحدث كله أو لا شيء، بمعنى أنه لا يُمكِن أن يكتمل جزئيًا. تحتوي معظم الحواسيب على عملياتٍ ذرية بمستوى لغة الآلة machine language level. قد تتوفَّر على سبيل المثال تعليمةً بلغة الآلة مسؤولةً عن زيادة قيمة موضعٍ معينٍ بالذاكرة بخطوةٍ واحدة. لا تعاني مثل تلك التعليمات من خطر حالات التسابق.
</p>

<p>
	بالنسبة للبرامج، يمكن لعمليةٍ أن تكون ذرية حتى لو لم تكن كذلك حرفيًا بمستوى لغة الآلة؛ حيث تُعدّ العملية ذريةً إذا لم يكن بإمكان أي خيطٍ أن يراها مكتملةً جزئيًا. على سبيل المثال، يحتوي الصنف <code>ThreadSafeCounter</code> المُعرَّف بالأعلى على عملية زيادةٍ ذرية. يُمكِننا أن نفكر بالمزامنة مثل طريقةٍ للتأكيد على كون العمليات ذرية. ومع ذلك، سيكون من الأفضل لو استطعنا الحصول على عملياتٍ ذرية دون استخدام المزامنة، خاصةً وأنه من الممكن تنفيذ تلك العمليات بكفاءة عالية على مستوى العتاد.
</p>

<p>
	تُوفِّر جافا حزمة <code>java.util.concurrent.atomic</code>، والتي تحتوي على أصنافٍ تُنفِّذ عملياتٍ ذرية على عدة أنواع متغيراتٍ بسيطة. سنفحص الصنف <code>AtomicInteger</code> الذي يُعرَّف بعض العمليات الذرية على قيمةٍ من النوع العددي الصحيح، بما في ذلك الجمع والزيادة والنقصان. لنفترض على سبيل المثال أننا نريد إضافة قيمٍ عدديةٍ صحيحة تنتجها مجموعةٌ من الخيوط. يُمكِننا فعل ذلك باستخدام الصنف <code>AtomicInteger</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_99_81" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="typ">AtomicInteger</span><span class="pln"> total </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">AtomicInteger</span><span class="pun">();</span></pre>

<p>
	يُنشَأ <code>total</code> بقيمةٍ مبدئية تُساوي الصفر. عندما يحاول خيطٌ معينٌ إضافة قيمةٍ ما إلى <code>total</code>، فيُمكِنه استخدام التابع <code>total.addAndGet(x)‎</code>، الذي يضيف <code>x</code> إلى <code>total</code>، ويعيد قيمة <code>total</code> الجديدة بعد الإضافة. يُعدّ ذلك مثالًا على عمليةٍ ذرية لا يمكن مقاطعتها، أي ستكون قيمة <code>total</code> صحيحةً بالضرورة. يُعَد البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest3.java" rel="external nofollow">ThreadTest3.java</a> نسخةً أخرى من البرنامج <a href="http://math.hws.edu/javanotes/source/chapter12/ThreadTest2.java" rel="external nofollow">ThreadTest2</a>، ولكنه يَستخدِم الصنف <code>AtomicInteger</code> بدلًا من المزامنة لحساب حاصل مجموع بعض القيم التي تنتجها خيوطٌ متعددة.
</p>

<p>
	يحتوي الصنف <code>AtomicInteger</code> على توابعٍ أخرى، مثل <code>total.incrementAndGet()‎</code> لإضافة <code>1</code> إلى <code>total</code>، و <code>total.decrementAndGet()‎</code> لطرح <code>1</code> منه. يضبُط التابع <code>total.getAndSet(x)‎</code> قيمة <code>total</code> إلى <code>x</code>، ويعيد القيمة السابقة التي حلّت <code>x</code> محلها. تحدث جميع تلك العمليات على الفور؛ إما لأنها تَستخدِم تعليمات لغة آلة ذرية؛ أو لكونها تَستخدِم المزامنة داخليًا.
</p>

<p>
	تحذير: لا يُعدّ اِستخدَام المتغيرات الذرية حلًا تلقائيًا لجميع حالات التسابق التي قد تشملها تلك المتغيرات. ألقِ نظرةً على الشيفرة التالية على سبيل المثال:
</p>

<pre class="ipsCode">
int currentTotal = total.addAndGet(x);
System.out.println("Current total is " + currentTotal);
</pre>

<p>
	ربما تكون قيمة <code>total</code> قد تغيرت بخيطٍ آخر بحلول وقت تنفيذ تعليمة الطباعة، وبذلك لا تكون <code>currentTotal</code> هي القيمة الحالية للمتغير <code>total</code>.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c12/s1.html" rel="external nofollow">Section 1: Introduction to Threads</a> من فصل Chapter 12: Threads and Multiprocessing من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D9%85%D8%AE%D8%AA%D8%B5%D8%B1%D8%A9-%D9%84%D9%84%D8%BA%D8%A9-xml-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D9%87%D8%A7-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1475/" rel="">مقدمة مختصرة للغة XML واستعمالها في تطبيقات جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1460/" rel="">معالجة الملفات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1483</guid><pubDate>Thu, 03 Mar 2022 16:00:02 +0000</pubDate></item><item><title>&#x645;&#x642;&#x62F;&#x645;&#x629; &#x645;&#x62E;&#x62A;&#x635;&#x631;&#x629; &#x644;&#x644;&#x63A;&#x629; XML &#x648;&#x627;&#x633;&#x62A;&#x639;&#x645;&#x627;&#x644;&#x647;&#x627; &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D9%85%D8%AE%D8%AA%D8%B5%D8%B1%D8%A9-%D9%84%D9%84%D8%BA%D8%A9-xml-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D9%87%D8%A7-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1475/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62062fa48dfa6_---XML.png.1637c9414950c2cbfc450233130089a6.png" /></p>

<p>
	عند تخزين بياناتٍ معينة بملفٍ معين أو نقلها خلال شبكةٍ معينة، سيكون من الضروري تمثيلها بطريقةٍ ما بشرط أن تَسمَح تلك الطريقة بإعادة بناء البيانات لاحقًا عند قراءة الملف أو عند تَسلُّم البيانات. رأينا بالفعل أسبابًا جيدةً تدفعنا لتفضيل التمثيلات النصية المُعتمِدة على المحارف، ومع ذلك هناك طرائقٌ كثيرةٌ لتمثيل مجموعةٍ معينةٍ من البيانات تمثيلًا نصيًا. سنُقدِّم بهذا المقال لمحةً مختصرةً عن أحد أكثر <a href="https://academy.hsoub.com/devops/servers/databases/%D9%86%D9%85%D8%B0%D8%AC%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r521/" rel="">أنواع تمثيل البيانات data representation</a> شيوعًا.
</p>

<p>
	تُعدّ لغة الترميز القابلة للامتداد eXtensible Markup Language -أو اختصارًا XML- أسلوبًا لإنشاء لغاتٍ لتمثيل البيانات. هناك جانبان أو مستويان بلغة XML؛ حيث تُخصِّص XML في المستوى الأول قواعد صيغة صارمةً ولكنها بسيطةً نوعًا ما، وتُعدّ أي متتالية محارف تَتبِع تلك القواعد مستند XML سليم؛ بينما تُوفِّر XML على المستوى الآخر طريقةً لإضافة المزيد من القيود على ما يُمكِن كتابته بالمستند من خلال ربط مستند XML معين بما يُعرَف باسم "تعريف نوع المستند Document Type Definition" -أو اختصارًا DTD- الذي يُخصِّص قائمةً بالأشياء المسموح بكتابتها بمستند XML.
</p>

<p>
	يُقال على مستندات XML السليمة المُرتبِطَة بتعريف نوع مستند معين والمُتبعِّة للقواعد التي خصَّصها ذلك التعريف "مستندات XML صالحة". يُمكِننا النظر إلى لغة XML كما لو كانت صيغةً عامةً لتمثيل البيانات؛ في حين تُخصِّص DTD الكيفية التي ينبغي بها استخدام XML لتمثيل نوعٍ معينٍ من البيانات. تتوفَّر بدائلٌ أخرى بخلاف DTD، مثل XML schemas لتعريف مستندات XML صالحة، ولكننا لن نناقشها هنا.
</p>

<p>
	لا يوجد شيءٌ سحريٌ بخصوص XML، فهي ليست مثالية، وتُعدُّ في الحقيقة لغةً مُسهبةً verbose ويراها البعض قبيحة، ولكنها من الناحية الأخرى مرنةٌ للغاية؛ حيث يُمكِن اِستخدَامها لتمثيل أي نوعٍ من البيانات تقريبًا. لقد صُمِّمت من البداية لتدعم جميع <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a>، والأهم من ذلك، أنها قد أصبحت بالفعل معيارًا مقبولًا على نطاقٍ واسع، فبإمكان جميع لغات البرمجة معالجة مستندات XML، بل ويتوفَّر تعريف نوع مستند DTD قياسي لوصف مختلف أنواع البيانات.
</p>

<p>
	هناك طرائقٌ مختلفةٌ كثيرة لتصميم لغةٍ لتمثيل مجموعةٍ من البيانات، ولكن حظت XML بشعبيةٍ واسعة، بل إنها حتى قد وجدت طريقها إلى جميع تقنيات المعلومات. على سبيل المثال، هناك لغات XML لتمثيل كُلٍ من التعبيرات الحسابية "MathML" والرموز الموسيقية "MusicXML" والجزيئات والتفاعلات الكيميائية "CML" ورسومات الفيكتور "SVG" وغيرها من المعلومات.
</p>

<p>
	بالإضافة إلى ذلك، تَستخِدم برامج OpenOffice والإصدارات الأحدث من Microsoft Office لغة XML بصيغ المستندات لتطبيقات معالجة النصوص وجداول البيانات spreadsheets والعروض. مثالٌ آخر هو لغة مشاركة المواقع "RSS, ATOM"، التي سهَّلت على المواقع الإلكترونية والمدونات والجرائد الإخبارية إنشاء قائمةٍ بالعناوين الأحدث المتاحة بصيغةٍ قياسيةٍ قابلةٍ للِاستخدَام بواسطة مواقع ومتصفحات الويب، وهي في الواقع نفس الصيغة المُستخدَمة لنشر المدونات الصوتية podcasts. تُعدّ XML عمومًا الصيغة الأكثر شيوعًا لتبادل المعلومات إلكترونيًا.
</p>

<p>
	ليس الهدف هنا هو إطلاعك على كل شيء يُمكِن معرفته عن XML، وإنما سنشرح فقط طرائقًا قليلةً يَصلُح معها استخدام XML بالبرامج؛ أي أننا لن نناقش أي شيءٍ آخر عن تعريفات نوع المستند DTD، أو مستندات XML الصالحة، وإنما سنَكتفِي باستخدام مستندات XML سليمة دون ربطها بأي تعريفاتٍ لنوع المستند DTD؛ فهي عادةً ما تكون كافيةً للعديد من الأغراض.
</p>

<h2>
	قواعد صيغة بسيطة للغة XML
</h2>

<p>
	إذا كنت تعرف لغة HTML -اللغة المُستخدَمة لكتابة صفحات الإنترنت-، فستبدو لغة XML مألوفةً بالنسبة لك، حيث تشبه مستندات XML كثيرًا مستندات HTML. لا تُعدّ <a href="https://wiki.hsoub.com/HTML" rel="external">لغة HTML</a> هي نفسها لغة XML؛ فهي لا تَتبِع جميع قواعد الصيغة الصارمة الخاصة بلغة XML، ولكن الأفكار الأساسية هي نفسها. نعرض فيما يلي مثالًا قصيرًا على مستند XML سليم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_10" style="">
<span class="pun">&lt;?</span><span class="pln">xml version</span><span class="pun">=</span><span class="str">"1.0"</span><span class="pun">?&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">simplepaint version</span><span class="pun">=</span><span class="str">"1.0"</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">background red</span><span class="pun">=</span><span class="str">'1'</span><span class="pln"> green</span><span class="pun">=</span><span class="str">'0.6'</span><span class="pln"> blue</span><span class="pun">=</span><span class="str">'0.2'</span><span class="pun">/&gt;</span><span class="pln">
   </span><span class="str">&lt;curve&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">color red</span><span class="pun">=</span><span class="str">'0'</span><span class="pln"> green</span><span class="pun">=</span><span class="str">'0'</span><span class="pln"> blue</span><span class="pun">=</span><span class="str">'1'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="str">&lt;symmetric&gt;</span><span class="kwd">false</span><span class="pun">&lt;/</span><span class="pln">symmetric</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'83'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'96'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'116'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'149'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'159'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'215'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'216'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'294'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'264'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'359'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'309'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'418'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'371'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'499'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'400'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'543'</span><span class="pun">/&gt;</span><span class="pln">
   </span><span class="pun">&lt;/</span><span class="pln">curve</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="str">&lt;curve&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">color red</span><span class="pun">=</span><span class="str">'1'</span><span class="pln"> green</span><span class="pun">=</span><span class="str">'1'</span><span class="pln"> blue</span><span class="pun">=</span><span class="str">'1'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="str">&lt;symmetric&gt;</span><span class="kwd">true</span><span class="pun">&lt;/</span><span class="pln">symmetric</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'54'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'305'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'79'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'289'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'128'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'262'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'190'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'236'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'253'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'209'</span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'341'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'158'</span><span class="pun">/&gt;</span><span class="pln">
   </span><span class="pun">&lt;/</span><span class="pln">curve</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">simplepaint</span><span class="pun">&gt;</span></pre>

<p>
	يُعدّ السطر الأول بالأعلى اختياريًا، حيث يُشير ببساطةٍ إلى أن ما يليه هو مستند XML. قد يتضمَّن هذا السطر معلوماتٍ أخرى أيضًا، مثل الترميز المُستخدَم لترميز محارف المستند إلى صيغةٍ ثنائية. وقد يتضمَّن المستند سطرًا للموجه "DOCTYPE" لتحديد تعريف نوع المستند.
</p>

<p>
	يتكوَّن المستند بخلاف السطر الأول من مجموعةٍ من العناصر elements والسمات attributes والمحتويات النصية. يبدأ أي عنصرٍ بوسم tag، مثل <code>&lt;curve&gt;</code> وينتهي بوسم الغلق المقابل، مثل <code>&lt;/curve&gt;</code>، ونجد محتويات العنصر بين هذين الوسمين، والتي يُمكِن أن تتكوَّن من نصٍ وعناصرٍ متداخلة أخرى.
</p>

<p>
	يُعدّ المحتوى النصي الوحيد بالمثال السابق هو القيمة <code>true</code> أو <code>false</code> بالعنصر <code>&lt;symmetric&gt;</code>. إذا لم يحتوِ عنصرٌ معينٌ على أي شيء، يُمكِننا دمج وسميه للفتح والغلق بوسمٍ واحدٍ فارغ بكتابة الاختصار <code>‎&lt;point x='83' y='96'/&gt;‎</code> بدلًا من <code>&lt;point x='83' y='96'&gt;&lt;/point&gt;</code>. لاحِظ استخدام "/" قبل "&lt;". قد يتضمَّن الوسم عِدّة سمات، مثل <code>x</code> و <code>y</code> بالوسم <code>‎&lt;point x='83' y='96'/&gt;‎</code>، و <code>version</code> بالوسم <code>&lt;simplepaint version="1.0"&gt;</code>. قد يتضمَّن المستند أشياءً قليلةً أخرى، مثل التعليقات ولكننا لن نناقشها هنا.
</p>

<p>
	ينبغي لأي مؤلف مستند XML سليم أن يختار أسماء الوسوم والسمات، وبطبيعة الحال عليه اختيار أسماءٍ ذات معنًى مفهوم للقارئ. في المقابل، إذا كان المستند يعتمد على تعريف نوع مستند DTD معين، فإن مؤلف التعريف هو من يختار أسماء الوسوم.
</p>

<p>
	يجب على أي مستند XML سليم أن يَتبِّع مجموعةً صارمةً من قواعد الصياغة syntax، نُلخِّص أهمها فيما يلي:
</p>

<ul>
<li>
		تُميِّز أسماء الوسوم والسمات بمستندات XML بين حالة الأحرف case sensitive؛ فقد تتكوَّن الأسماء من مجموعةٍ من الأحرف والأرقام وبعض المحارف الأخرى، ولكن لا بُدّ أن تبدأ بحرف.
	</li>
	<li>
		ليس للفراغات ومحرف نهاية السطر أي أهمية إلا إذا وقعت بالمحتويات النصية؛ فعند كتابة وسمٍ معينٍ، ولم يَكُن هذا الوسم فارغًا، فيجب إذًا أن يُقابِله وسم غلق؛ ويَعنِي ذلك أنه إذا تداخلت عدة عناصرٍ مع بعضها، فيجب أن يكون التداخل سليمًا. بتعبيرٍ آخر، إذا تضمَّن عنصرٌ معينٌ وسمًا، فيجب أن يَقع وسم غلقه ضمن نفس العنصر.
	</li>
	<li>
		لا بُدّ أن يحتوي أي مستندٍ على عنصر جذر root، والذي يحتوي بدوره على جميع العناصر الأخرى. لاحِظ أن اسم الوسم المُمثِّل لعنصر الجذر بالمثال السابق، هو <code>simplepaint</code>.
	</li>
	<li>
		لا بُدّ أن يكون لأي سمةٍ قيمة، والتي ينبغي أن تكون محاطةً بعلامتي اقتباس إما مفردة أو مزدوجة.
	</li>
	<li>
		في حالة اِستخدَام المحارف الخاصة <code>&gt;</code> و <code>&amp;</code> بقيمةٍ لسمة أو بمحتوى نصي، فيجب أن تُكْتَب على النحو التالي <code>‎&amp;gt;‎</code> و <code>‎&amp;quot;‎</code>. أمثلة أخرى على ذلك، هي <code>‎&amp;gt;‎</code> و <code>‎&amp;quot;‎</code> و <code>‎&amp;apos;‎</code>، والتي تُمثِّل <code>&gt;</code> وعلامة اقتباسٍ مزدوجة وعلامة اقتباسٍ مفردة على الترتيب. يُمكِن تعريف المزيد بتعريف DTD.
	</li>
</ul>
<p>
	يُعدّ ما سَبَق مقدمةً سريعةً عن مستندات XML، والتي ربما لن تساعدك على فهم كل شيء تقابله بمستند XML، ولكنها ستُمكِّنك على الأقل من تصميم مستندات XML سليمة لتمثيل بعض بنى البيانات data structures المُستخدَمة ببرامج جافا.
</p>

<h2>
	العمل مع شجرة DOM
</h2>

<p>
	صمَّمنا في الأعلى مثال XML لتخزين معلوماتٍ عن رسومٍ بسيطة يَرسِمها المُستخدِم، واعتمدنا على البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter7/SimplePaint2.java" rel="external nofollow">SimplePaint2.java</a> من مقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D9%8A%D9%86%D8%A7%D9%85%D9%8A%D9%83%D9%8A%D8%A9-arraylists-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1159/" rel="">مفهوم المصفوفات الديناميكية (ArrayLists) في جافا</a> فيما هو مُتعلِّقٌ برسم تلك الرسوم. سنناقش بهذا المقال نسخةً أخرى من نفس البرنامج، والتي يُمكِنها حفظ رسوم المُستخدِم بملف بياناتٍ معتمِدٍ على صيغة XML. يُمكِنك الإطلاع على شيفرة النسخة الجديدة من <a href="http://math.hws.edu/javanotes/source/chapter11/SimplePaintWithXML.java" rel="external nofollow">SimplePaintWithXML.java</a>.
</p>

<p>
	سنَستخدِم مستند XML التوضيحي الذي عرضناه بالأعلى ضمن هذا البرنامج. في الحقيقة، لقد صمَّمنا هذا المستند بحيث يحتوي على جميع البيانات المطلوبة لإعادة إنشاء الكائن المُمثِّل للرسمة، والذي ينتمي للصنف <code>SimplePaint</code>؛ حيث يتضمَّن المستند على سبيل المثال معلوماتٍ عن لون خلفية الصورة وكذلك قائمةً بالمنحنيات التي رَسَمها المُستخدِم. لاحِظ احتواء كل عنصر <code>&lt;curve&gt;</code> على بيانات كائنٍ من النوع <code>CurveData</code>.
</p>

<p>
	من السهل كتابة برنامج يُخرِج البيانات بصيغة XML، مع الحرص طبعًا على اتباع جميع قواعد صيغة XML، حيث تبيّن الشيفرة التالية الطريقة التي يَستخدِمها الصنف <code>SimplePaintWithXML</code> لإرسال بيانات رسمةٍ من النوع <code>SimplePaint</code> إلى <code>out</code> من النوع <code>PrintWriter</code>. يَنتُج عن ذلك مستند XML بنفس هيئة المثال المُوضَح بالأعلى:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_13" style="">
<span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"&lt;?xml version=\"1.0\"?&gt;"</span><span class="pun">);</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"&lt;simplepaint version=\"1.0\"&gt;"</span><span class="pun">);</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"   &lt;background red='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> backgroundColor</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"' green='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
        backgroundColor</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"' blue='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> backgroundColor</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"'/&gt;"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">CurveData</span><span class="pln"> c </span><span class="pun">:</span><span class="pln"> curves</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"   &lt;curve&gt;"</span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"      &lt;color red='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"' green='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
            c</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"' blue='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"'/&gt;"</span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"      &lt;symmetric&gt;"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">+</span><span class="pln"> </span><span class="str">"&lt;/symmetric&gt;"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Point2D</span><span class="pln"> pt </span><span class="pun">:</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">points</span><span class="pun">)</span><span class="pln">
        out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"      &lt;point x='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getX</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"' y='"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getY</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"'/&gt;"</span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"   &lt;/curve&gt;"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"&lt;/simplepaint&gt;"</span><span class="pun">);</span></pre>

<p>
	قراءة البيانات من مستند XML إلى البرنامج مرةً أخرى هي عمليةٌ أخرى تمامًا، حيث ينبغي تحليل parse المستند واستخراج البيانات منه؛ لنتمكَّن من إعادة إنشاء بنية البيانات المُمثِلة للمستند، وهو ما يتطلّب الكثير من العمل. تُوفِّر جافا لحسن الحظ واجهة برمجة تطبيقات قياسية standard <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> لتحليل مستندات XML ومعالجتها، وهي تُوفِّر في الواقع واجهتين ولكننا سنتعامل مع واحدةٍ منهما فقط.
</p>

<p>
	يتكوَّن أي مستند XML سليم من بنيةٍ معروفةٍ مكوَّنة من مجموعةٍ من العناصر elements مُتضمَّنةً مجموعةً من السمات attributes؛ والعناصر المتداخلة؛ والمحتويات النصية. يُمكِننا إنشاء بنية بياناتٍ مكافئةً لبنية المستند ومحتوياته في ذاكرة الحاسوب، حيث تتوفَّر الكثير من الطرائق لذلك، ولكن يُعدّ تمثيل <strong>نموذج كائن المستند Document Object Model</strong> -أو اختصارًا DOM- أكثرها شيوعًا؛ حيث يُخصِّص هذا النموذج طريقةً لبناء بنية بياناتٍ مكافئة لمستندات XML؛ كما أنه يُعرِّف مجموعةً من التوابع methods القياسية، التي تَسمَح باسترجاع البيانات الموجودة ضمن تلك البنية.
</p>

<p>
	تُعدّ تلك البنية أشبه ما تكون بشجرةٍ تُكافئ بنيتها بنية المستند، حيث تتكوَّن الشجرة من أنواعٍ مختلفة من العُقد nodes المُستخدَمة لتمثيل العناصر والسمات والنصوص، كما قد تحتوي على أنواعٍ آخرى من العُقد لتمثيل جوانبٍ أخرى من XML. سنُركز فقط على العقد المُمثِّلة للعناصر بسبب عدم القدرة على معالجة السمات والنصوص مباشرةً دون معالجة ما يُقابِلها من عُقد.
</p>

<p>
	يُمكِّنك البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter11/XMLDemo.java" rel="external nofollow">XMLDemo.java</a> من التعامل مع كُلٍ من XML و DOM. يتضمَّن البرنامج صندوقًا نصيًا حيث يُمكِنك كتابة مستند XML، كما يحتوي على مستند XML توضيحي من هذا المقال افتراضيًا. إذا ضغطت على الزر "Parse XML Input"، سيقرأ البرنامج المستند المكتوب بالصندوق، وسيحاول بناء تمثيل DOM له. إذا لم تَكْن المُدْخلات مُمثِّلةً لمستند XML سليم، فستظهر رسالة خطأ؛ أما إذا كانت سليمة، فسيجتاز البرنامج تمثيل DOM بالكامل، وسيَعرِض قائمةً بكل العناصر والسمات والمحتويات النصية التي سيُقابِلها. يَستخدِم البرنامج بعض التقنيات لمعالجة XML، ولكننا لن نناقشها هنا.
</p>

<p>
	يُمكِننا إنشاء تمثيل DOM لمستند XML باستخدام التعليمتين التاليتين. إذا كان <code>selectedFile</code> متغيرًا من النوع <code>File</code> يُمثِّل مستند XML، فسيكون <code>xmldoc</code> من النوع <code>Document</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_16" style="">
<span class="typ">DocumentBuilder</span><span class="pln"> docReader 
                 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">DocumentBuilderFactory</span><span class="pun">.</span><span class="pln">newInstance</span><span class="pun">().</span><span class="pln">newDocumentBuilder</span><span class="pun">();</span><span class="pln">
xmldoc </span><span class="pun">=</span><span class="pln"> docReader</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">selectedFile</span><span class="pun">);</span></pre>

<p>
	تَفتَح الشيفرة السابقة الملف، وتقرأ محتوياته، ثم تَبنِي تمثيل DOM. ستَجِد الصنفين <code>DocumentBuilder</code> و <code>DocumentBuilderFactory</code> بحزمة <code>javax.xml.parsers</code>. يُحاوِل التابع <code>docReader.parse()‎</code> إجراء العملية، ويُبلِّغ عن اعتراضٍ إذا لم يتمكَّن من قراءة الملف أو إذا لم يحتوِ الملف على مستند XML صالح. في المقابل، إذا نجح التابع، فإنه يعيد كائنًا يُمثِّل كامل المستند. تُعدّ هذه العملية معقدةً بالتأكيد، ولكنها بُرمجَت بالكامل مرةً واحدةً، وضُمِّنَت بتابعٍ يُمكِنك اِستخدَامه بسهولة بأي برنامج جافا، وهو ما يُمثِّل إحدى فوائد الاعتماد على صيغةٍ قياسية.
</p>

<p>
	يُمكِنك الإطلاع على تعريف بنية البيانات DOM بحزمة <code>org.w3c.dom</code>، والتي تحتوي على أنواع بياناتٍ مختلفة لتمثيل مستند XML بالكامل؛ وكذلك لتمثيل العقد الموجودة بالمستند على حدى. يشير الاسم "org.w3c" إلى اتحاد شبكة الويب العالمية World Wide Web Consortium -أو اختصارًا <strong>W3C</strong>، والمسؤولة عن تنظيم المعايير القياسية لتقنيات الويب.
</p>

<p>
	يُعدّ تمثيل DOM مثل XML معيارًا عامًا؛ أي لا يختص <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-java-%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%D8%9F-r371/" rel="">بلغة جافا</a> دون غيرها. سنحتاج بهذا المثال أنواع البيانات التالية <code>Document</code> و <code>Node</code> و <code>Element</code> و <code>NodeList</code>، والمُعرَّفة جميعًا على أنها واجهات interfaces لا أصناف، ولكن هذا غير مهم. سنَستخدِم التوابع المُعرَّفة بتلك الأنواع للوصول إلى البيانات الموجودة بتمثيل DOM لمستند XML.
</p>

<p>
	يعيد التابع <code>docReader.parse()‎</code> قيمةً من النوع <code>Document</code>، والتي كانت <code>xmldoc</code> في المثال السابق، وهي تُمثِّل مستند XML بالكامل. سنحتاج إلى التابع التالي فقط من هذا الصنف. إذا كان <code>xmldoc</code> من النوع <code>Document</code>، يُمكِننا كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_19" style="">
<span class="pln">xmldoc</span><span class="pun">.</span><span class="pln">getDocumentElement</span><span class="pun">()</span></pre>

<p>
	يُعيد هذا التابع قيمةً من النوع <code>Element</code> تُمثِّل عنصر الجذر root الخاص بالمستند؛ وهوالعنصر الموجود بأعلى مستوى بالمستند كما ذكرنا مُسبقًا، ويتضمَّن كافة العناصر الأخرى. يتكوَّن عنصر الجذر بمستند XML الخاص بالمثال السابق من هذا المقال من الوسمين <code>&lt;simplepaint version="1.0"‎&gt;</code> و <code>&lt;/simplepaint&gt;</code>، وكل ما هو موجودٌ بينهما. تُمثَّل العناصر الواقعة داخل عنصر الجذر باستخدام عقد nodes، والتي تُعدّ بمثابة أبناء عقدة الجذر. تتضمَّن كائنات النوع <code>Element</code> مجموعةً من التوابع المفيدة، والتي سنستعرِض بعضًا منها فيما يلي. إذا كان <code>element</code> من النوع <code>Element</code>، يُمكِننا استخدام التوابع التالية:
</p>

<ul>
<li>
		<code>element.getTagName()‎</code>: يُعيد سلسلةً نصيةً من النوع <code>String</code> للاسم المُستخدَم بالوسم الخاص بالعنصر؛ حيث سيُعيد على سبيل المثال السلسلة النصية "curve" للعنصر <code>&lt;curve&gt;</code>.
	</li>
	<li>
		<code>element.getAttribute(attrName)‎</code>: إذا كان <code>attrName</code> اسم سمةٍ في العنصر، سيُعيد التابع قيمة تلك السمة. على سبيل المثال، يعيد الاستدعاء <code>element.getAttribute("x")‎</code> السلسلة النصية "83" للعنصر <code>&lt;point x="83" y="42"/‎&gt;</code>، وتكون القيمة المعادة من النوع <code>String</code> دائمًا حتى لو كانت السمة تُمثِّل قيمة عددية. إذا لم يحتوِ عنصرٌ معين على سمةٍ للاسم المُخصَّص، فسيعيد التابع سلسلةً نصيةً فارغة.
	</li>
	<li>
		<code>element.getTextContent()‎</code>: يُعيد سلسلةً نصيةً من النوع <code>String</code> تحتوي على المحتوى النصي الموجود بالعنصر، بما في ذلك محتوى العناصر الأخرى الواقعة ضمن العنصر.
	</li>
	<li>
		<code>element.getChildNodes()‎</code>: يعيد قيمةً من النوع <code>NodeList</code> تحتوي على جميع العُقد الأبناء لذلك العنصر. تتضمَّن القائمة العقد المُمثِلة لأي عنصرٍ آخر أو محتوى نصي (إلى جانب أنواع أخرى من العقد) مُتداخِل مباشرةً مع العنصر. يَسمَح التابع <code>getChildNodes()‎</code> باجتياز بنية البيانات DOM بالكامل بدءًا من عنصر الجذر، مرورًا بأبنائه، وأبناء أبنائه، وهكذا. يتوفَّر تابعٌ آخر يعيد جميع سمات العنصر، ولكننا لن نَستخدِمه هنا.
	</li>
	<li>
		<code>element.getElementsByTagName(tagName)‎</code>: يُعيد قيمةً من النوع <code>NodeList</code> تحتوي على جميع العُقد المُمثِّلة لعناصر واقعةٍ داخل <code>element</code>، والتي لها نفس اسم الوسم المُخصَّص. يتضمَّن ذلك العناصر الواقعة بأي مستوى داخل <code>element</code>، وليس فقط تلك المتصلة مباشرةً معه. يَسمَح التابع <code>getElementsByTagName()‎</code> بالوصول إلى بيانات مُحدَّدة ضمن المستند.
	</li>
</ul>
<p>
	يُمثِّل كائنٌ من النوع <code>NodeList</code> قائمةً من العقدة من النوع <code>Node</code>، ولكنه لا يَستخدِم واجهة برمجة التطبيقات الخاصة بالقوائم والمُعرَّفة بإطار عمل جافا للتجميعات. يُعرِّف الصنف <code>NodeList</code> بدلًا من ذلك التابعين التاليين: <code>nodeList.getLength()‎</code> و <code>nodeList.item(i)‎</code>؛ حيث يُعيد الأول عدد العقد ضمن القائمة؛ بينما يُعيد الثاني العقدة الموجودة بموضع <code>i</code>، والذي تتراوح قيمه من <code>0</code> حتى <code>nodeList.getLength() - 1</code>. يُعيد التابع <code>nodeList.get()‎</code> قيمةً من النوع <code>Node</code>، والتي يُمكِن تحويلها type-cast إلى نوعٍ مُخصّصٍ من العقد قبل استخدامها.
</p>

<p>
	بناءً على هذه المعلومات، يُمكِنك إجراء أغلب أنواع العمليات الممكنة على تمثيلات DOM. لنفحص الآن بعض الشيفرة. لنفترض أنه وبينما تعالج إحدى المستندات، قد توصلت إلى عقدةٍ من النوع <code>Element</code> تُمثِّل العنصر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_21" style="">
<span class="pun">&lt;</span><span class="pln">background red</span><span class="pun">=</span><span class="str">'1'</span><span class="pln"> green</span><span class="pun">=</span><span class="str">'0.6'</span><span class="pln"> blue</span><span class="pun">=</span><span class="str">'0.2'</span><span class="pun">/&gt;</span></pre>

<p>
	قد يقابلنا العنصر السابق إما أثناء اجتياز المستند باستخدام <code>getChildNodes()‎</code> أو نتيجةً لاستدعاء التابع <code>getElementsByTagName("background")‎</code>. علينا الآن إعادة إنشاء بنية البيانات التي يُمثِّلها المستند، والتي يُعدّ هذا العنصر جزءًا من بياناتها؛ حيث يُمثِل لون الخلفية تحديدًا بواسطة ثلاث سمات attributes تُمثِّل مكوّنات اللون الأحمر والأخضر والأزرق. إذا كان <code>element</code> متغيرًا يُشير إلى تلك العقدة، يُمكِننا استرجاع اللون بكتابة ما يَلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_23" style="">
<span class="kwd">double</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"red"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="kwd">double</span><span class="pln"> g </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"green"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="kwd">double</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"blue"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="typ">Color</span><span class="pln"> bgColor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln">g</span><span class="pun">,</span><span class="pln">b</span><span class="pun">);</span></pre>

<p>
	لنفترض الآن أن <code>element</code> يشير إلى العقدة المُمثِّلة للعنصر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_25" style="">
<span class="str">&lt;symmetric&gt;</span><span class="kwd">true</span><span class="pun">&lt;/</span><span class="pln">symmetric</span><span class="pun">&gt;</span></pre>

<p>
	يُمثِل <code>element</code> في تلك الحالة قيمة مُتغيّر من النوع المنطقي boolean، ولكنها رُمزَّت كأنها محتوًى نصي للعنصر. يُمكِننا استرجاع تلك القيمة من العنصر بكتابة ما يَلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_27" style="">
<span class="typ">String</span><span class="pln"> </span><span class="kwd">bool</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getTextContent</span><span class="pun">();</span><span class="pln">
boolean symmetric</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">bool</span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"true"</span><span class="pun">))</span><span class="pln">
   symmetric </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">else</span><span class="pln">
   symmetric </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span></pre>

<p>
	لنفكر الآن بمثالٍ يَستخدِم كائنًا من الصنف <code>NodeList</code>. إذا واجهنا العنصر التالي الذي ينبغي تمثيله بقائمة عناصر تنتمي للصنف <code>Point2D</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_29" style="">
<span class="str">&lt;pointlist&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'17'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'42'</span><span class="pun">/&gt;</span><span class="pln">   
   </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'23'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'8'</span><span class="pun">/&gt;</span><span class="pln">   
   </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'109'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'342'</span><span class="pun">/&gt;</span><span class="pln">   
   </span><span class="pun">&lt;</span><span class="pln">point x</span><span class="pun">=</span><span class="str">'18'</span><span class="pln"> y</span><span class="pun">=</span><span class="str">'270'</span><span class="pun">/&gt;</span><span class="pln">   
</span><span class="pun">&lt;/</span><span class="pln">pointlist</span><span class="pun">&gt;</span></pre>

<p>
	لنفترض أن <code>element</code> يشير إلى العقدة المُمثِّلة للعنصر <code>&lt;pointlist&gt;</code>، علينا الآن إنشاء قائمةٍ من النوع <code>ArrayList&lt;Point2D&gt;‎</code> لتمثيله، حيث تجتاز الشيفرة التالية قائمة الصنف <code>NodeList</code> المُتضمِّنة لعقد أبناء العنصر على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_31" style="">
<span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Point2D</span><span class="pun">&gt;</span><span class="pln"> points </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">
</span><span class="typ">NodeList</span><span class="pln"> children </span><span class="pun">=</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getChildNodes</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> children</span><span class="pun">.</span><span class="pln">getLength</span><span class="pun">();</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">Node</span><span class="pln"> child </span><span class="pun">=</span><span class="pln"> children</span><span class="pun">.</span><span class="pln">item</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">   </span><span class="com">// أحد عقد أبناء العنصر</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> child instanceof </span><span class="typ">Element</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">Element</span><span class="pln"> pointElement </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Element</span><span class="pun">)</span><span class="pln">child</span><span class="pun">;</span><span class="pln">  </span><span class="com">// One of the &lt;point&gt; elements.</span><span class="pln">
      </span><span class="typ">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">.</span><span class="pln">parseInt</span><span class="pun">(</span><span class="pln"> pointElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"x"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
      </span><span class="typ">int</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">.</span><span class="pln">parseInt</span><span class="pun">(</span><span class="pln"> pointElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"y"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
       </span><span class="com">// ‫أنشئ النقطة التي يُمثِلها pointElement</span><span class="pln">
      </span><span class="typ">Point2D</span><span class="pln"> pt </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">);</span><span class="pln"> 
      points</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">pt</span><span class="pun">);</span><span class="pln">  </span><span class="com">// أضف النقطة إلى قائمة النقاط</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُعدّ جميع عناصر <code>&lt;point&gt;</code> الواقعة داخل العنصر <code>&lt;pointlist&gt;</code> أبناءً له. يجب أن نَستخدِم تعليمة <code>if</code> -كما بالأعلى- نظرًا لإمكانية احتواء العنصر على أبناء تنتمي لأصناف أخرى غير الصنف <code>Element</code>، والتي نحن في الواقع غير مهتمين بمعالجتها ضمن هذا المثال.
</p>

<p>
	يُمكِننا توظيف كل تلك التقنيات لكتابة تابعٍ يقرأ ملف الدْخَل بالبرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter11/SimplePaintWithXML.java" rel="external nofollow">SimplePaintWithXML.java</a>. عندما نُنشِئ بنية بياناتٍ لتمثيل ملف XML، يُفضَّل أن نبدأ ببنية بياناتٍ افتراضية، والتي يُمكِننا تعديلها، والإضافة إليها بينما نجتاز شجرة DOM المُمثِلة للملف. في الواقع، هذه العملية ليست سهلة نوعًا ما، ولذلك حاول أن تقرأها بعناية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5181_33" style="">
<span class="typ">Color</span><span class="pln"> newBackground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">WHITE</span><span class="pun">;</span><span class="pln">
</span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">CurveData</span><span class="pun">&gt;</span><span class="pln"> newCurves </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">
</span><span class="typ">Element</span><span class="pln"> rootElement </span><span class="pun">=</span><span class="pln"> xmldoc</span><span class="pun">.</span><span class="pln">getDocumentElement</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> rootElement</span><span class="pun">.</span><span class="pln">getNodeName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"simplepaint"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"File is not a SimplePaint file."</span><span class="pun">);</span><span class="pln">
</span><span class="typ">String</span><span class="pln"> version </span><span class="pun">=</span><span class="pln"> rootElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"version"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">double</span><span class="pln"> versionNumber </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">version</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">versionNumber </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1.0</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"File requires a newer version of SimplePaint."</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">NumberFormatException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="typ">NodeList</span><span class="pln"> nodes </span><span class="pun">=</span><span class="pln"> rootElement</span><span class="pun">.</span><span class="pln">getChildNodes</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> nodes</span><span class="pun">.</span><span class="pln">getLength</span><span class="pun">();</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">nodes</span><span class="pun">.</span><span class="pln">item</span><span class="pun">(</span><span class="pln">i</span><span class="pun">)</span><span class="pln"> instanceof </span><span class="typ">Element</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">Element</span><span class="pln"> element </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Element</span><span class="pun">)</span><span class="pln">nodes</span><span class="pun">.</span><span class="pln">item</span><span class="pun">(</span><span class="pln">i</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">element</span><span class="pun">.</span><span class="pln">getTagName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"background"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="kwd">double</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"red"</span><span class="pun">));</span><span class="pln">
         </span><span class="kwd">double</span><span class="pln"> g </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"green"</span><span class="pun">));</span><span class="pln">
         </span><span class="kwd">double</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">element</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"blue"</span><span class="pun">));</span><span class="pln">
         newBackground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln">g</span><span class="pun">,</span><span class="pln">b</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">element</span><span class="pun">.</span><span class="pln">getTagName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"curve"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">CurveData</span><span class="pln"> curve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pun">();</span><span class="pln">
         curve</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">BLACK</span><span class="pun">;</span><span class="pln">
         curve</span><span class="pun">.</span><span class="pln">points </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">
         newCurves</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">curve</span><span class="pun">);</span><span class="pln">
         </span><span class="typ">NodeList</span><span class="pln"> curveNodes </span><span class="pun">=</span><span class="pln"> element</span><span class="pun">.</span><span class="pln">getChildNodes</span><span class="pun">();</span><span class="pln">
         </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> j </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> j </span><span class="pun">&lt;</span><span class="pln"> curveNodes</span><span class="pun">.</span><span class="pln">getLength</span><span class="pun">();</span><span class="pln"> j</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">curveNodes</span><span class="pun">.</span><span class="pln">item</span><span class="pun">(</span><span class="pln">j</span><span class="pun">)</span><span class="pln"> instanceof </span><span class="typ">Element</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
             </span><span class="typ">Element</span><span class="pln"> curveElement </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Element</span><span class="pun">)</span><span class="pln">curveNodes</span><span class="pun">.</span><span class="pln">item</span><span class="pun">(</span><span class="pln">j</span><span class="pun">);</span><span class="pln">
             </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getTagName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"color"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="kwd">double</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"red"</span><span class="pun">));</span><span class="pln">
               </span><span class="kwd">double</span><span class="pln"> g </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"green"</span><span class="pun">));</span><span class="pln">
               </span><span class="kwd">double</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"blue"</span><span class="pun">));</span><span class="pln">
               curve</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln">g</span><span class="pun">,</span><span class="pln">b</span><span class="pun">);</span><span class="pln">
             </span><span class="pun">}</span><span class="pln">
             </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getTagName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"point"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="kwd">double</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"x"</span><span class="pun">));</span><span class="pln">
               </span><span class="kwd">double</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Double</span><span class="pun">.</span><span class="pln">parseDouble</span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"y"</span><span class="pun">));</span><span class="pln">
               curve</span><span class="pun">.</span><span class="pln">points</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">));</span><span class="pln">
             </span><span class="pun">}</span><span class="pln">
             </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">curveElement</span><span class="pun">.</span><span class="pln">getTagName</span><span class="pun">().</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"symmetric"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
               </span><span class="typ">String</span><span class="pln"> content </span><span class="pun">=</span><span class="pln"> curveElement</span><span class="pun">.</span><span class="pln">getTextContent</span><span class="pun">();</span><span class="pln">
               </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">content</span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"true"</span><span class="pun">))</span><span class="pln">
                 curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
             </span><span class="pun">}</span><span class="pln">
           </span><span class="pun">}</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">         
</span><span class="pun">}</span><span class="pln">
backgroundColor </span><span class="pun">=</span><span class="pln"> newBackground</span><span class="pun">;</span><span class="pln">
curves </span><span class="pun">=</span><span class="pln"> newCurves</span><span class="pun">;</span></pre>

<p>
	يُمكِنك الإطلاع على كامل الشيفرة المصدرية في الملف <a href="http://math.hws.edu/javanotes/source/chapter11/SimplePaintWithXML.java" rel="external nofollow">SimplePaintWithXML.java</a>.
</p>

<p>
	تطوَّرت XML لتُصبِح واحدةً من أهم التقنيات المُستخدَمة لتطوير بعض أعقد التطبيقات، وهناك مع ذلك بعض الأفكار الأساسية البسيطة التي يَسهُل تطبيقها بجافا. بمجرد إطلاعك على أساسيات XML، يُمكِنك استخدامها بفعالية ضمن برامج جافا الخاصة بك.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c11/s5.html" rel="external nofollow">Section 5: A Brief Introduction to XML</a> من فصل Chapter 11: Input/Output Streams, Files, and Networking من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/" rel="">تواصل تطبيقات جافا عبر الشبكة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">الدليل السريع للغة البرمجة Java</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%83%D8%AA%D8%A8-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r372/" rel="">اكتب برنامجك الأول بلغة جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1475</guid><pubDate>Thu, 24 Feb 2022 16:08:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x623;&#x634;&#x62C;&#x627;&#x631; &#x627;&#x644;&#x628;&#x62D;&#x62B; &#x627;&#x644;&#x62B;&#x646;&#x627;&#x626;&#x64A;&#x629; &#x648;&#x627;&#x644;&#x623;&#x634;&#x62C;&#x627;&#x631; &#x627;&#x644;&#x645;&#x62A;&#x632;&#x646;&#x629; balanced trees &#x644;&#x62A;&#x646;&#x641;&#x64A;&#x630; &#x627;&#x644;&#x62E;&#x631;&#x627;&#x626;&#x637;</title><link>https://academy.hsoub.com/programming/java/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B4%D8%AC%D8%A7%D8%B1-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D9%84%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A3%D8%B4%D8%AC%D8%A7%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D9%86%D8%A9-balanced-trees-%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-r1454/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61f7a40285b40_-------.png.5021b565864155172f9ed58fef0170b5.png" /></p>
<p>
	سنناقش في هذا المقال حل تمرين مقالة <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%AC%D8%B1%D8%A9-%D8%A8%D8%AD%D8%AB-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-treemap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1453/" rel="">تحليل زمن تشغيل الخرائط المُنفَّذة باستخدام شجرة بحثٍ ثنائيّةٍ</a>، ونختبر أداء الخرائط المُنفّذة باستخدام شجرة، وبعدها سنناقش إحدى مشاكل ذلك التنفيذ والحلّ الذي يقدمه الصنف <code>TreeMap</code> لتلك المشكلة.
</p>

<h2>
	الصنف MyTreeMap
</h2>

<p>
	وفَّرنا في المقالة المشار إليها تصوّرًا مبدئيًا للصنف <code>MyTreeMap</code>، وتركنا للقارئ مهمة إكمال توابعه. وسنُكملها الآنَ معًا، ولْنبدأ بالتابع <code>findNode</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_7" style=""><span class="kwd">private</span><span class="pln"> </span><span class="typ">Node</span><span class="pln"> findNode</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> target</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// some implementations can handle null as a key, but not this one</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">target </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IllegalArgumentException</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// something to make the compiler happy</span><span class="pln">
    </span><span class="lit">@SuppressWarnings</span><span class="pun">(</span><span class="str">"unchecked"</span><span class="pun">)</span><span class="pln">
    </span><span class="typ">Comparable</span><span class="pun">&lt;?</span><span class="pln"> super K</span><span class="pun">&gt;</span><span class="pln"> k </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Comparable</span><span class="pun">&lt;?</span><span class="pln"> super K</span><span class="pun">&gt;)</span><span class="pln"> target</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// the actual search</span><span class="pln">
    </span><span class="typ">Node</span><span class="pln"> node </span><span class="pun">=</span><span class="pln"> root</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node </span><span class="pun">!=</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> cmp </span><span class="pun">=</span><span class="pln"> k</span><span class="pun">.</span><span class="pln">compareTo</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cmp </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
            node </span><span class="pun">=</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">left</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cmp </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
            node </span><span class="pun">=</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">right</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> node</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَستخدِم التابعان <code>containsKey</code> و <code>get</code> التابعَ <code>findNode</code>، ولأنه ليس جزءًا من الواجهة <code>Map</code>، عرَّفناه باستخدام المُعدّل <code>private</code>. يُمثِل المعامل <code>target</code> المفتاحَ الذي نبحث عنه. كنا قد شرحنا الجزء الأول من هذا التابع في المقال المشار إليه:
</p>

<ul>
	<li>
		لا تُعدّ <code>null</code> قيمةً صالحةً كمفتاحٍ في هذا التنفيذ.
	</li>
	<li>
		ينبغي أن نحوِّلَ نوعَ المعاملِ <code>target</code> إلى <code>Comparable</code> قبل أن نَستدعِيَ تابعَه <code>compareTo</code>. اِستخدَمنا أكثر أنواع محارف البدل عمومية، حيث يَعمَل مع أي نوع يُنفِّذ الواجهة <code>Comparable</code>، كما أن تابعه <code>compareTo</code> يَستقبِل النوع <code>K</code> أو أيًّا من أنواعه الأعلى supertype.
	</li>
</ul>

<p>
	يُجرَى البحث على النحو التالي: نضبط متغير الحلقة <code>node</code> إلى عقدة الجذر، وفي كلّ تكرارٍ، نوازن بين المفتاح <code>target</code> وقيمة <code>node.key</code>. إذا كان <code>target</code> أصغرَ من مفتاح العقدة الحاليّة، سننتقل إلى عقدة الابن اليسرى، أما إذا كان أكبرَ منه، سننتقل إلى عقدة الابن اليمنى، وإذا كانا متساويين، سنعيد العقدة الحاليّة.
</p>

<p>
	إذا وصلنا إلى قاع الشجرة دون أن نعثر على المفتاح المطلوب، فهذا يَعنِي أنه غير موجود فيها، وسنعيد في تلك الحالة القيمة الفارغة <code>null</code>.
</p>

<h2>
	البحث عن القيم values
</h2>

<p>
	كما أوضحنا في نفس المقالة المشار إليها في الأعلى، يتناسب زمن تنفيذ التابع <code>findNode</code> مع ارتفاع الشجرة وليس مع عدد العقد الموجودة فيها؛ وذلك لأننا غير مضطرّين للبحث في كامل الشجرة، ولكن بالنسبة للتابع <code>containsValue</code>، فإننا سنضطرّ للبحث بالقيم وليس المفاتيح، ولأن خاصية BST لا تُطبَّق على القيم، فإننا سنضطرّ إلى البحث في كامل الشجرة.
</p>

<p>
	يَستخدِم الحلُّ التالي التعاودَ recursion:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_10" style=""><span class="kwd">public</span><span class="pln"> boolean containsValue</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> target</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> containsValueHelper</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> target</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">private</span><span class="pln"> boolean containsValueHelper</span><span class="pun">(</span><span class="typ">Node</span><span class="pln"> node</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> target</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">equals</span><span class="pun">(</span><span class="pln">target</span><span class="pun">,</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">value</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">containsValueHelper</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> target</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">containsValueHelper</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">right</span><span class="pun">,</span><span class="pln"> target</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَستقبِل التابعُ <code>containsValue</code> المعاملَ <code>target</code>، ويُمرِّره مع معاملٍ إضافيٍّ يحتوي على عقدة الجذر إلى التابع <code>containsValueHelper</code>.
</p>

<p>
	يَعمَل التابع <code>containsValueHelper</code> وفقًا لما يلي:
</p>

<ul>
	<li>
		تفحص تعليمة <code>if</code> الأولى الحالة الأساسية للتعاود: إذا كانت قيمة <code>node</code> مساويةً للقيمة الفارغة <code>null</code>، فإن التابع وصل إلى قاع الشجرة دون إيجاد القيمة المطلوبة <code>target</code>، ويعيد عندها القيمة <code>false</code>. انتبه، يعني ذلك أن القيمة <code>target</code> غير موجودةٍ في واحدٍ فقط من مسارات الشجرة لا في مسارات الشجرة كلّها، ولذا ما يزال من الممكن العثور عليها في مسارٍ آخر.
	</li>
	<li>
		تفحص تعليمة <code>if</code> الثانية ما إذا كان التابع قد وجد القيمة المطلوبة، وفي تلك الحالة، يعيد التابع القيمة <code>true</code>، أما إذا لم يجدها، فإنه يستمر في البحث.
	</li>
	<li>
		تَستدعِي الحالة الشرطية الثالثة التابعَ تعاوديًا لكي يبحث عن نفس القيمة، أي <code>target</code>، في الشجرة الفرعية اليسرى. إذا وجدها فيها، فإنه يعيد القيمة <code>true</code> مباشرةً دون أن يحاول البحث في الشجرة الفرعية اليمنى، أما إذا لم يجدها فيها، فإنه يستمر في البحث.
	</li>
	<li>
		تبحث الحالة الشرطية الرابعة عن القيمة المطلوبة في الشجرة الفرعية اليمنى. إذا وجدها فيها، فإنه يعيد القيمة <code>true</code>، أما إذا لم يجدها، فإنه يعيد القيمة <code>false</code>.
	</li>
</ul>

<p>
	يمرّ التابع السابق عبر كل عقدةٍ من الشجرة، ولهذا، يَستغرِق زمنًا يتناسب مع عدد العقد.
</p>

<h2>
	تنفيذ التابع put
</h2>

<p>
	يُعدّ التابع <code>put</code> أعقد قليلاً من التابع <code>get</code>؛ لأن عليه أن يتعامل مع حالتين: الأولى عندما يكون المفتاح المُعطَى موجودًا في الشجرة بالفعل، وينبغي عندها أن يَستبدِله ويعيد القيمة القديمة، والثانية عندما لا يكون موجودًا، وعندها ينبغي أن يُنشِئ عقدةً جديدةً ثم يضعها في المكان الصحيح.
</p>

<p>
	كنا قد وفّرنا الشيفرة المبدئية التالية لذلك التابع في المقالة المذكورة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_12" style=""><span class="kwd">public</span><span class="pln"> V put</span><span class="pun">(</span><span class="pln">K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IllegalArgumentException</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">root </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
        size</span><span class="pun">++;</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> putHelper</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وكان المطلوب هو إكمال متن التابع <code>putHelper</code>. انظر إلى شيفرته فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_14" style=""><span class="kwd">private</span><span class="pln"> V putHelper</span><span class="pun">(</span><span class="typ">Node</span><span class="pln"> node</span><span class="pun">,</span><span class="pln"> K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Comparable</span><span class="pun">&lt;?</span><span class="pln"> super K</span><span class="pun">&gt;</span><span class="pln"> k </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Comparable</span><span class="pun">&lt;?</span><span class="pln"> super K</span><span class="pun">&gt;)</span><span class="pln"> key</span><span class="pun">;</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> cmp </span><span class="pun">=</span><span class="pln"> k</span><span class="pun">.</span><span class="pln">compareTo</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">key</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cmp </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">left </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            node</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
            size</span><span class="pun">++;</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> putHelper</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cmp </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">right </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            node</span><span class="pun">.</span><span class="pln">right </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
            size</span><span class="pun">++;</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> putHelper</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">right</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    V oldValue </span><span class="pun">=</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
    node</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> oldValue</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُضبَط المعاملُ الأوّلُ <code>node</code> مبدئيًا إلى عقدة الجذر root، وفي كل مرةٍ نَستدعِي فيها التابع تعاوديًّا، يشير المعامل إلى شجرةٍ فرعيّةٍ مختلفةٍ. مثل التابع <code>get</code>، اِستخدَمنا التابع <code>compareTo</code> لتحديد المسار الذي سنتبعه في الشجرة. إذا تحقّق الشرط <code>cmp &lt; 0</code>، يكون المفتاح المطلوب إضافته أقلّ من <code>node.key</code>، وعندها يكون علينا فحص الشجرة الفرعية اليسرى. هنالك حالتان:
</p>

<ul>
	<li>
		إذا كانت الشجرةُ الفرعيّةُ فارغةً، فإن <code>node.left</code> تحتوي على <code>null</code>، وعندها نكون قد وصلنا إلى قاع الشجرة دون أن نعثر على المفتاح <code>key</code>. في تلك الحالة، نكون قد تأكّدنا من أن المفتاح <code>key</code> غير موجود في الشجرة، وعرفنا المكان الذي ينبغي أن نضيف المفتاح إليه، ولذلك، نُنشِئ عقدةً جديدةً، ونضيفها كعقدةٍ ابنةٍ يسرى للعقدة <code>node</code>.
	</li>
	<li>
		إن لم تكن الشجرة فارغةً، نَستدعِي التابع تعاوديًّا للبحث في الشجرة الفرعية اليسرى.
	</li>
</ul>

<p>
	في المقابل، إذا تحقّق الشرط <code>cmp &gt; 0</code>، يكون المفتاح المطلوب إضافته أكبر من <code>node.key</code>، وعندها يكون علينا فحص الشجرة الفرعية اليمنى، وسيكون علينا معالجة نفس الحالتين السابقتين. أخيرًا، إذا تحقّق الشرط <code>cmp == 0</code>، نكون قد عثرنا على المفتاح داخل الشجرة، وعندها، نستطيع أن نستبدله ونعيد القيمة القديمة.
</p>

<p>
	كتبنا هذا التابع تعاوديًّا لكي نُسهِّل من قراءته، ولكن يُمكِن كتابته أيضًا بأسلوبٍ تكراريٍّ. يُمكِنك القيام بذلك كتمرين.
</p>

<h2>
	اجتياز في الترتيب In-order
</h2>

<p>
	كنا قد طلبنا منك كتابة التابع <code>keySet</code> لكي يعيد مجموعةً من النوع <code>Set</code> تحتوي على مفاتيح الشجرة مُرتَّبة تصاعديًّا. لا يعيد هذا التابع في التنفيذات الأخرى من الواجهة <code>Map</code> المفاتيح وفقًا لأيّ ترتيب، ولكن لأن هذا التنفيذَ يتمتع بالبساطة والكفاءة، فإنه يَسمَح لنا بترتيب المفاتيح، وعلينا أن نَستفيد من ذلك.
</p>

<p>
	انظر إلى شيفرة التابع فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_16" style=""><span class="kwd">public</span><span class="pln"> </span><span class="typ">Set</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;</span><span class="pln"> keySet</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Set</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">set</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinkedHashSet</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;();</span><span class="pln">
    addInOrder</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> </span><span class="typ">set</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">set</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> addInOrder</span><span class="pun">(</span><span class="typ">Node</span><span class="pln"> node</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Set</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">set</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    addInOrder</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> </span><span class="typ">set</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">set</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
    addInOrder</span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">right</span><span class="pun">,</span><span class="pln"> </span><span class="typ">set</span><span class="pun">);</span><span class="pln">        
</span><span class="pun">}</span></pre>

<p>
	كما ترى فقد أنشأنا قيمةً من النوع <code>LinkedHashSet</code> في التابع <code>keySet</code>. يُنفِّذ ذلك النوع الواجهة <code>Set</code> ويحافظ على ترتيب العناصر (بخلاف معظم تنفيذات تلك الواجهة). نَستدعِي بعد ذلك التابع <code>addInOrder</code> لاجتياز الشجرة.
</p>

<p>
	يشير المعامل الأول <code>node</code> مبدئيًّا إلى جذر الشجرة، ونَستخدِمه -كما يُفترَض أن تتوقَّع- لاجتياز الشجرة تعاوديًا. يجتاز التابع <code>addInOrder</code> الشجرة بأسلوب "في الترتيب" المعروف.
</p>

<p>
	إذا كانت العقدة <code>node</code> فارغةً، يَعنِي ذلك أن الشجرةَ الفرعيةَ فارغةٌ، وعندها يعود التابع دون إضافة أيّ شيءٍ إلى المجموعة <code>set</code>، أما إذا لم تكن فارغةً، نقوم بما يلي:
</p>

<ol>
	<li>
		نجتاز الشجرة الفرعية اليسرى بالترتيب.
	</li>
	<li>
		نضيف <code>node.key</code>.
	</li>
	<li>
		نجتاز الشجرة الفرعية اليمنى بالترتيب.
	</li>
</ol>

<p>
	تذكّر أن خاصية BST تضمن أن تكون جميع العقد الموجودة في الشجرة الفرعية اليسرى أقلَّ من <code>node.key</code> وأن تكون جميع العقد الموجودة في الشجرة الفرعية اليمنى أكبرَ منه، أي أننا نضيف <code>node.key</code> إلى الترتيب الصحيح.
</p>

<p>
	بتطبيق نفس المبدأ تعاوديًا، نستنتج أن عناصر الشجرة الفرعية اليسرى واليمنى مُرتَّبة، كما أن الحالة الأساسية صحيحة: إذا كانت الشجرة الفرعية فارغةً، فإننا لا نضيف أيّة مفاتيح. يَعنِي ما سبق أن هذا التابعَ يضيف جميع المفاتيح وفقًا لترتيبها الصحيح.
</p>

<p>
	ولأن هذا التابع يمرّ عبر كل عقدةٍ ضمن الشجرة مثله مثل التابع <code>containsValue</code>، فإنه يَستغرِق زمنًا يتناسب مع <code>n</code>.
</p>

<h2>
	التوابع اللوغاريتمية
</h2>

<p>
	يَستغرِق التابعان <code>get</code> و <code>put</code> في الصنف <code>MyTreeMap</code> زمنًا يتناسب مع ارتفاع الشجرة h. أوضحنا في المقالة المشار إليها أنه إذا كانت الشجرة ممتلئةً أي كان كل مستوىً منها يحتوي على الحد الأقصى من عدد العقد المسموح به، فإن ارتفاع تلك الشجرة يكون متناسبًا مع log(n)‎.
</p>

<p>
	نفترض الآن أن التابعين <code>get</code> و <code>set</code> يستغرقان زمنًا لوغاريتميًا، أي زمنًا يتناسب مع log(n)‎، مع أننا لا نَضمَن أن تكون الشجرة ممتلئةً دائماً. يعتمد شكل الشجرة في العموم على المفاتيح وعلى الترتيب الذي تُضاف به.
</p>

<p>
	سنختبر التنفيذ الذي كتبناه بمجموعتي بيانات لكي نرى كيف يعمل. المجموعة الأولى عبارةٌ عن قائمةٍ تحتوي على <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">سلاسلَ نصيّةٍ</a> عشوائيّةٍ، والثانية عبارةٌ عن قائمةٍ تحتوي على علاماتٍ زمنيّةٍ timestamp مُرتَّبةٍ تصاعديًّا.
</p>

<p>
	تُولِّد الشيفرةُ التاليةُ السلاسلَ النصيّة العشوائية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_19" style=""><span class="typ">Map</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">map</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">MyTreeMap</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;();</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">&lt;</span><span class="pln">n</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pln"> uuid </span><span class="pun">=</span><span class="pln"> UUID</span><span class="pun">.</span><span class="pln">randomUUID</span><span class="pun">().</span><span class="pln">toString</span><span class="pun">();</span><span class="pln">
    </span><span class="typ">map</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">uuid</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يقع تعريف الصنف <code>UUID</code> ضمن حزمة <code>java.util</code>، ويُمكِنه أن يُولِّد مُعرِّف هويةٍ فريدًا عموميًا universally unique identifier بأسلوبٍ عشوائي. تُعدّ تلك المُعرّفات ذاتَ فائدةٍ كبيرةٍ في مختلِف أنواع التطبيقات، ولكننا سنَستخدِمها في هذا المثال كطريقةٍ سهلةٍ لتوليد سلاسلَ نصيّةٍ عشوائيّةٍ.
</p>

<p>
	شغّلنا الشيفرة التالية مع <code>n=16384</code> وحسبنا زمن التنفيذ وارتفاع الشجرةِ النهائيّ، وحصلنا على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_21" style=""><span class="typ">Time</span><span class="pln"> in milliseconds </span><span class="pun">=</span><span class="pln"> </span><span class="lit">151</span><span class="pln">
</span><span class="typ">Final</span><span class="pln"> size of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16384</span><span class="pln">
log base </span><span class="lit">2</span><span class="pln"> of size of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14.0</span><span class="pln">
</span><span class="typ">Final</span><span class="pln"> height of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">33</span></pre>

<p>
	أضفنا أيضًا قيمة اللوغاريتم للأساس 2 إلى الخريطة لكي نرى طول الشجرة إذا كانت ممتلئة. تشير النتيجة إلى أن شجرةً ممتلئةً بارتفاعٍ يساوي 14 تحتوي على 16,384 عقدة.
</p>

<p>
	في الواقع، ارتفاع شجرة السلاسل النصية العشوائية الفعلي هو 33، وهو أكبر من الحد الأدنى النظري ولكن ليس بشكل كبير. لكي نعثر على مفتاحٍ ضمن تجميعةٍ مكونةٍ من 16,384 عقدةً، سنضطرّ لإجراء 33 موازنةً، أي أسرع بـ 500 مرةً تقريبًا من البحث الخطي linear search.
</p>

<p>
	يُعدّ هذا الأداء نموذجيًا للسلاسل النصيّة العشوائيّة والمفاتيح الأخرى التي لا تضاف وفقًا لأيّ ترتيب. رغم أن ارتفاع الشجرة النهائيّ يصل إلى ضعف الحدَ النظريَ الأدنى أو ثلاثةِ أضعافِه، فهو ما يزال متناسبًا مع log(n)‎، أي أقل بكثير من n، حيث تنمو قيمة log(n)‎ ببطءٍ مع زيادة قيمة n لدرجةٍ يَصعُب معها التمييز بين الزمن الثابت والزمن اللوغاريتمي عمليًا.
</p>

<p>
	في المقابل، لا تَعمَل أشجار البحث الثنائية بهذه الكفاءة دائمًا. لنرى ما قد يحدث عند إضافة المفاتيح بترتيبٍ تصاعديٍّ. يَستخدِم المثال التالي علاماتٍ زمنيةً -بوحدة النانو ثانية- كمفاتيح:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_23" style=""><span class="typ">MyTreeMap</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">map</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">MyTreeMap</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">&gt;();</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">&lt;</span><span class="pln">n</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pln"> timestamp </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Long</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">(</span><span class="typ">System</span><span class="pun">.</span><span class="pln">nanoTime</span><span class="pun">());</span><span class="pln">
    </span><span class="typ">map</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">timestamp</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يعيد <code>System.nanoTime</code> عددًا صحيحًا من النوع <code>long</code> يشير إلى الزمن المُنقضِي بوحدة النانو ثانية. نحصل على عددٍ أكبرَ قليلًا في كلّ مرةٍ نَستدعيه فيها. عندما نُحوِّل تلك العلامات الزمنية إلى سلاسلَ نصيّةٍ، فإنها تكون مُرتَّبة أبجديًّا.
</p>

<p>
	لنرى ما نحصل عليه عند التشغيل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1522_25" style=""><span class="typ">Time</span><span class="pln"> in milliseconds </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1158</span><span class="pln">
</span><span class="typ">Final</span><span class="pln"> size of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16384</span><span class="pln">
log base </span><span class="lit">2</span><span class="pln"> of size of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">14.0</span><span class="pln">
</span><span class="typ">Final</span><span class="pln"> height of </span><span class="typ">MyTreeMap</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">16384</span></pre>

<p>
	يتجاوز زمن التشغيل في هذه الحالة سبعةَ أضعاف زمن التشغيل في الحالة السابقة. إذا كنت تتساءل عن السبب، فألق نظرةً على ارتفاع الشجرةِ النهائيّ 16384.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91070" href="https://academy.hsoub.com/uploads/monthly_2022_01/001Balanced_Unbalanced_BST.PNG.b343beb74555967a45362e9adb3f2319.PNG" rel="" data-fileext="PNG"><img alt="001Balanced_Unbalanced_BST.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="91070" data-unique="ai1bbtyvv" src="https://academy.hsoub.com/uploads/monthly_2022_01/001Balanced_Unbalanced_BST.PNG.b343beb74555967a45362e9adb3f2319.PNG"></a>
</p>

<p>
	إذا أمعنت النظر في الطريقة التي يَعمَل بها التابع <code>put</code>، فقد تفهم ما يحدث: ففي كل مرةٍ نضيف فيها مفتاحًا جديدًا، فإنه يكون أكبر من جميع المفاتيح الموجودة في الشجرة، وبالتالي، نضطرّ دائمًا لاختيار الشجرة الفرعية اليمنى، ونضيف دائمًا العقدة الجديدة كعقدة ابن يمنى للعقدة الواقعة على أقصى اليمين. نحصل بذلك على شجرة غير متزنة unbalanced تحتوي على عقدٍ أبناء يمنى فقط.
</p>

<p>
	يتناسب ارتفاع تلك الشجرة مع n وليس log(n)‎، ولذلك يصبح أداء التابعين <code>get</code> و <code>set</code> خطيًا لا لوغاريتميًا.
</p>

<p>
	تَعرِض الصورة السابقة مثالًا عن شجرتين إحداهما متزنة والأخرى غير متزنة. يُمكِننا أن نرى أن ارتفاع الشجرة المتزنة يساوي 4 وعدد العقد الكلية يساوي 2<sup>4</sup>−1 أي 15. تحتوي الشجرة غير المتزنة على نفس عدد العقد، ولكن ارتفاعها يساوي 15.
</p>

<h2>
	الأشجار المتزنة ذاتيا Self-balancing trees
</h2>

<p>
	هناك حلّان محتملان لتلك المشكلة:
</p>

<ul>
	<li>
		يُمكِننا أن نتجنّب إضافة المفاتيح إلى الخريطة بالترتيب، ولكن هذا الحل ليس ممكنًا دائمًا.
	</li>
	<li>
		يُمكِننا أن نُنشِئ شجرةً قادرةً على التعامل مع المفاتيح المرتّبة تعاملًا أفضل.
	</li>
</ul>

<p>
	يبدو الحل الثاني أفضل، وتتوفّر طرائقُ عديدةٌ لتنفيذه. يُمكِننا مثلًا أن نُعدّل التابع <code>put</code> لكي نجعله يفحص ما إذا كانت الشجرة قد أصبحت غير متزنة، وعندها، يعيد ترتيب العقد. يُطلَق على الأشجار التي تتميز بتلك المقدرة اسم "الأشجار المتزنة ذاتيًا"، ومن أشهرها شجرة AVL (اختصار Adelson-Velskii Tree حيث إن Adelson و Velskii هما مبتكرا هذه الشجرة)، وشجرة red-black التي يَستخدِمها صنف <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">الجافا</a> <code>TreeMap</code>.
</p>

<p>
	إذا استخدمنا الصنف <code>TreeMap</code> بدلًا من الصنف <code>MyTreeMap</code> في الشيفرة السابقة، سيصبح زمن تشغيل مثالِ السلاسل النصية ومثالِ العلامات الزمنية هو نفسه، بل في الحقيقة، سيكون مثال العلامات الزمنية أسرع على الرغم من أن المفاتيح مُرتَّبة؛ لأنه يَستغرِق وقتًا أقل لحساب شيفرة <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashing-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1416/" rel="">التعمية hash</a>.
</p>

<p>
	نستخلص مما سبق أن أشجار البحث الثنائية قادرةٌ على تنفيذ التابعين <code>get</code> و <code>put</code> بزمن لوغاريتمي بشرط إضافة المفاتيح إليها وفقًا لترتيبٍ يحافظ على اتزانها بشكلٍ كافٍ. في المقابل، تتجنَّب الأشجار المتزنة ذاتيًا تلك المشكلة بإنجاز بعض العمل الإضافي في كلّ مرةٍ يُضاف فيها مفتاح جديد.
</p>

<p>
	يُمكِنك قراءة المزيد عن <a href="http://thinkdast.com/balancing" rel="external nofollow">الأشجار المتزنة ذاتيًّا</a>.
</p>

<h2>
	تمرين إضافي
</h2>

<p>
	لم نُنفِّذ التابع <code>remove</code> في ذاك التمرين، ولكن يُمكِنك أن تُجرِّب كتابته الآن. إذا حذفت عقدةً من وسط الشجرة، ستضطرّ إلى إعادة ترتيب العقد المتبقية لكي تحافظ على خاصية BST. 
</p>

<p>
	تُعدّ عمليتا حذف عقدةٍ وإعادة الشجرة إلى الاتزان عمليتين متشابهتين، لذا إذا أتممت هذا التمرين، ستفهم طريقة عمل الأشجار المتزنة ذاتيًا فهماً أعمق.
</p>

<p>
	ترجمة -بتصرّف- للفصل <a href="https://greenteapress.com/thinkdast/html/thinkdast014.html" rel="external nofollow">Chapter 13: Binary search tree</a> من كتاب <a href="https://greenteapress.com/thinkdast/html/index.html" rel="external nofollow">Think Data Structures: Algorithms and Information Retrieval in Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/devops/servers/databases/redis/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-redis-%D9%84%D8%AD%D9%81%D8%B8-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r579/" rel="">استخدام قاعدة بيانات Redis لحفظ البيانات</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%AC%D8%B1%D8%A9-%D8%A8%D8%AD%D8%AB-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-treemap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1453/" rel="">تحليل زمن تشغيل الخرائط المنفذة باستخدام شجرة بحث ثنائية TreeMap في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%A3%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashmap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1452/" rel="">تحسين أداء الخرائط المنفذة باستخدام التعمية HashMap في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1389/" rel="">تحليل زمن تشغيل الخرائط المنفذة باستخدام مصفوفة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%B1%D9%8A%D8%B7%D8%A9-%D9%88%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%81%D9%87%D8%B1%D8%B3-indexer-r1384/" rel="">استخدام خريطة ومجموعة لبناء مفهرس Indexer</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1454</guid><pubDate>Sat, 19 Feb 2022 16:06:00 +0000</pubDate></item><item><title>&#x62A;&#x648;&#x627;&#x635;&#x644; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x62C;&#x627;&#x641;&#x627; &#x639;&#x628;&#x631; &#x627;&#x644;&#x634;&#x628;&#x643;&#x629;</title><link>https://academy.hsoub.com/programming/java/%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-r1474/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62062bd1c1ac3_61f839336fa72_.png.853c523eb57d586ec7ab4b1f9b520a17(1).png.1bad65c0f0b84f2e7fbbd2825ebd8d81.png" /></p>

<p>
	لا تُعدّ الشبكات -بقدر ما يَهُمّ البرامج- أكثر من مجرد مصدرٍ يُمكِن قراءة البيانات منه، أو مقصدٍ يُمكِن إرسال البيانات إليه. يُسطِّح ذلك الأمور إلى درجةٍ كبيرة؛ فليس التعامل مع الشبكات بالتأكيد بنفس سهولة التعامل مع الملفات مثلًا، وتُمكِّنك لغة جافا مع ذلك من إجراء الاتصالات الشبكية باستخدام مجاري تدفق streams الدْخل والخرج بنفس الطريقة التي تَستخدِمها بها للتواصل مع المُستخدِم أو لمعالجة الملفات.
</p>

<p>
	بالرغم من ذلك، تنطوي عملية إجراء اتصال شبكي بين حاسوبين على الكثير من التعقيدات؛ حيث ينبغي أن يتفقا بطريقةٍ ما على فتح اتصالٍ بينهما. بالإضافة إلى ذلك، إذا كان كلُ حاسوبٍ قادرًا على إرسال البيانات إلى الحاسوب الآخر، تُصبِح مزامنة التواصل بينهما مشكلةً أخرى، ولكن تُعدّ الأساسيات في العموم هي نفسها.
</p>

<p>
	تُعدّ <code>java.net</code> إحدى حزم packages جافا القياسية، حيث تتضمَّن عدَّة أصنافٍ للتعامل مع الشبكات، كما تدعم طريقتين مختلفتين لإجراء عمليات الدخل والخرج خلال الشبكة. تعتمد الطريقة الأولى عالية المستوى high level على شبكة الإنترنت العالمية World Wide Web، وتُوفِّر إمكانياتٍ للاتصال الشبكي مماثلةً لتلك الإمكانيات التي يَستخدِمها متصفح الإنترنت عند تحميله للصفحات. يُعدّ <code>java.net.URL</code> و <code>java.net.URLConnection</code> الصنفين الأساسين المُستخدَمين مع هذا النوع من الشبكات.
</p>

<p>
	يُمثِّل أي كائنٍ من النوع <code>URL</code> تمثيلًا مجرّدًا لمُحدِّد مورد مُوحَّد Universal Resource Locator؛ فقد يكون عنوانًا لمستند <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a>، أو لأي موردٍ آخر؛ بينما يُمثِّل أي كائنٍ من النوع <code>URLConnection</code> اتصالًا شبكيًا مع إحدى تلك الموارد.
</p>

<p>
	من الجهة الأخرى، تَنظر الطريقة الثانية الأكثر عمومية وأهمية للشبكة بمستوى منخفض قليلًا low level؛ حيث تعتمد على فكرة المقابس socket المُستخدَمة لإنشاء اتصالٍ مع برنامجٍ آخر خلال الشبكة. يشتمل الاتصال عبر الشبكة على مقبسين، واحدٌ في كل طرف من طرفي الاتصال، وتَستخدِم جافا الصنف <code>java.net.Socket</code> لتمثيل المقابس المُستخدَمة بالاتصال الشبكي.
</p>

<p>
	أُخِذَت كلمة مِقبس من التصور المادي لتوصيل الحاسوب بسلكٍ بهدف ربطه بشبكة، ولكن ما نعنيه بالمقبس هنا أنه كائنٌ ينتمي إلى الصنف <code>Socket</code>. يَستطيع أي برنامجٍ أن يحتوي على مجموعةٍ من المقابس بنفس الوقت؛ بحيث يتَصِل كُلٌ منها ببرنامجٍ آخرٍ مُشغَّلٍ على حاسوبٍ آخر ضمن الشبكة، أو ربما على نفس الحاسوب. تَعتمِد جميع تلك الاتصالات على الاتصال الشبكي المادي نفسه.
</p>

<p>
	يتناول هذا مقال مقدمةً مُختصرة عن أساسيات أصناف الشبكات، كما يشرح علاقتها بمجاري الدْخَل والخرج.
</p>

<h2>
	محددات الموارد والصنفان URL و URLConnection
</h2>

<p>
	يُستخدَم الصنف <code>URL</code> لتمثيل الموارد resources بشبكة الويب العالمية World Wide Web، حيث يَملُك كل موردٍ منها عنوانًا يُميّزها، والذي يحتوي على معلوماتٍ كافية تُمكِّن متصفح الويب من العثور على المورد على الشبكة واسترجاعه. يُعرَف هذا العنوان باسم "محدِّد الموارد المُوحَّد universal resource locator - URL"، كما يُمكِنه في الواقع أن يشير إلى مواردٍ من مصادر أخرى غير الويب، فقد يُشير مثلًا إلى إحدى ملفات الحاسوب.
</p>

<p>
	يُمثِّل أي كائنٍ ينتمي إلى الصنف <code>URL</code> عنوانًا معينًا، وبمجرد حصولك على واحدٍ من تلك الكائنات، يُمكِنك إنشاء كائنٍ من الصنف <code>URLConnection</code> للاتصال مع المورد الموجود بذلك العنوان. يُكْتَب محدِّد الموارد المُوحَّد عادةً بهيئة سلسلةٍ نصية، مثل "http://math.hws.edu/eck/index.html"، ولكن هناك أيضًا محدِّدات مواردٍ نسبية relative url، والتي يُمكِنها تخصيص موقع موردٍ معين بالنسبة لموقع موردٍ آخر، والذي يَعمَل في تلك الحالة كأنه أساسٌ أو سياقٌ لمحدِّد المورد النسبي؛ فإذا كان السياق هو "http://math.hws.edu/eck/" مثلًا، فسيُشير المورد النسبي غير الكامل "index.html" في تلك الحالة إلى الآتي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2767_7" style="">
<span class="pln"> </span><span class="str">"http://math.hws.edu/eck/index.html"</span></pre>

<p>
	لاحِظ أن كائنات الصنف <code>URL</code> ليست مجرد سلاسلٍ نصية، ولكن يُمكِن إنشاؤها من التمثيل النصي لمحدِّد موردٍ موحَّد، كما يُمكِن إنشاء تلك الكائنات بالاستعانة بكائن <code>URL</code> آخر يُوفِّر سياقًا معينًا مع سلسلةٍ نصيةٍ تُوفِّر مُحدِّد المورد النسبي لذلك السياق. انظر تعريف البناة constructors المُعرَّفة بالصنف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_10" style="">
<span class="kwd">public</span><span class="pln"> URL</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> urlName</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">MalformedURLException</span></pre>

<p>
	و
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_12" style="">
<span class="kwd">public</span><span class="pln"> URL</span><span class="pun">(</span><span class="pln">URL context</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> relativeName</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">MalformedURLException</span></pre>

<p>
	حيث تُبلِّغ تلك البناة عن استثناء exception من النوع <code>MalformedURLException</code>، إذا لم تكن السلاسل النصية المُمرَّرة إليها مُمثِلةً لمحدِّدات مواردٍ مُوحَّدةٍ سليمة. لاحِظ أن الصنف <code>MalformedURLException</code> هو صنفٌ فرعيٌ من الصنف <code>IOException</code>، أي أنه يتطلَّب معالجةً إلزاميةً للاستثناءات.
</p>

<p>
	بمجرد حصولنا على كائن <code>URL</code> سليم، يُمكِننا استدعاء تابِعه <code>openConnection()‎</code> لإجراء اتصالٍ معه؛ حيث يُعيد هذا التابع كائنًا من النوع <code>URLConnection</code>، والذي يُمكِننا استدعاء تابعه <code>getInputStream()‎</code> لإنشاء كائنٍ من النوع <code>InputStream</code>، ونَستطيع بذلك قراءة بيانات المورد الذي يُمثِّله الكائن. انظر الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_15" style="">
<span class="pln">URL url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="pln">urlAddressString</span><span class="pun">);</span><span class="pln">
</span><span class="typ">URLConnection</span><span class="pln"> connection </span><span class="pun">=</span><span class="pln"> url</span><span class="pun">.</span><span class="pln">openConnection</span><span class="pun">();</span><span class="pln">
</span><span class="typ">InputStream</span><span class="pln"> in </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">();</span></pre>

<p>
	قد يُبلِّغ التابعان <code>openConnection()‎</code> و <code>getInputStream()‎</code> عن استثناؤات من النوع <code>IOException</code>. بمجرد إنشاء كائن الصنف <code>InputStream</code>، يُمكِننا أن نقرأ بياناته كما ناقشنا بالأقسام السابقة، بما في ذلك تضمينه داخل مجاري الدخل input stream من أنواعٍ أخرى، مثل <code>BufferedReader</code> أو <code>Scanner</code>. قد تؤدي قراءة بيانات المجرى إلى حدوث استثناءات بالتأكيد.
</p>

<p>
	يتضمَّن الصنف <code>URLConnection</code> توابع نسخ instance methods أخرى مفيدة؛ حيث يُعيد التابع <code>getContentType()‎</code> مثلًا سلسلةً نصيةً من النوع <code>String</code> تَصِف نوع المعلومات الموجودة بالمورد الذي يُمثِله كائن الصنف <code>URL</code>، كما يُمكِنه إعادة القيمة <code>null</code> إذا لم تكن نوعية المعلومات معروفةً بعد، أو لم يكن تحديد نوعها ممكنًا؛ أي قد لا نتمكَّن من معرفة نوع المعلومات حتى نُنشِئ مجرى المْدْخَلات باستدعاء التابع <code>getInputStream()‎</code> ثم التابع <code>getContentType()‎</code>، حيث يُعيد هذا التابع سلسلةً نصيةً بصيغةٍ تُعرَف باسم نوع الوسيط "mime type"، مثل "text/plain" و "text/html" و "image/jpeg" و "image/png" وغيرها.
</p>

<p>
	تتكوَّن جميع أنواع الوسائط من جزئين: نوعٌ عام، مثل "text" أو "image"، ونوعٌ أكثر تحديدًا من النوع العام، مثل "html" أو "png"؛ فإذا كنت مهتمًا بالبيانات النصية فقط مثلًا، يُمكِنك فحص فيما إذا بدأت السلسلة النصية المُعادة من التابع <code>getContentType()‎</code> بكلمة "text". كان الهدف الأساسي من أنواع الوسائط هو مجرد وصف محتويات رسائل البريد الإلكتروني؛ فالاسم "mime" هو أساسًا اختصارٌ لعبارة "Multipurpose Internet Mail Extensions"، ولكنها مُستخدَمةٌ الآن على نطاقٍ واسع لتحديد نوع المعلومات الموجودة بملفٍ أو بموردٍ آخر عمومًا.
</p>

<p>
	لنناقش الآن مثالًا قصيرًا على استخدام كائنٍ من النوع <code>URL</code> لقراءة البيانات الموجودة بمُحدِّد موردٍ معين، حيث يُنشِئ البرنامج الفرعي التالي اتصالًا مع المورد الذي يُمثِّله الكائن، ثم يَتأكَّد من أن البيانات التي يُشير إليها مُحدِّد المورد من النوع النصي، ويَنسَخ بعد ذلك النص إلى الشاشة. قد تُبلِّغ الكثير من العمليات المُضمَّنة بالبرنامج عن استثناءات، ولذلك أضفنا عبارة "throws IOException" أثناء التصريح عن البرنامج الفرعي؛ لنترك القرار للبرنامج المُستدعِي باختيار ما ينبغي فعله عند حدوث خطأٍ معين:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_17" style="">
<span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> readTextFromURL</span><span class="pun">(</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> urlString </span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">IOException</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

     </span><span class="com">// 1</span><span class="pln">

     URL url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="pln">urlString</span><span class="pun">);</span><span class="pln">
     </span><span class="typ">URLConnection</span><span class="pln"> connection </span><span class="pun">=</span><span class="pln"> url</span><span class="pun">.</span><span class="pln">openConnection</span><span class="pun">();</span><span class="pln">
     </span><span class="typ">InputStream</span><span class="pln"> urlData </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">();</span><span class="pln">

     </span><span class="com">// 2</span><span class="pln">

     </span><span class="typ">String</span><span class="pln"> contentType </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getContentType</span><span class="pun">();</span><span class="pln">
     </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Stream opened with content type: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> contentType</span><span class="pun">);</span><span class="pln">
     </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">();</span><span class="pln">
     </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">contentType </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">||</span><span class="pln"> contentType</span><span class="pun">.</span><span class="pln">startsWith</span><span class="pun">(</span><span class="str">"text"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
          </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"URL does not seem to refer to a text file."</span><span class="pun">);</span><span class="pln">
     </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Fetching context from "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> urlString </span><span class="pun">+</span><span class="pln"> </span><span class="str">" ..."</span><span class="pun">);</span><span class="pln">
     </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">();</span><span class="pln">

     </span><span class="com">// 3</span><span class="pln">

     </span><span class="typ">BufferedReader</span><span class="pln"> in</span><span class="pun">;</span><span class="pln">  </span><span class="com">// للقراءة من مجرى الدخل الخاص بالاتصال</span><span class="pln">

    in </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedReader</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">InputStreamReader</span><span class="pun">(</span><span class="pln">urlData</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

     </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="typ">String</span><span class="pln"> line </span><span class="pun">=</span><span class="pln"> in</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">
          </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">line </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
               </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
          </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">line</span><span class="pun">);</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
     in</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end readTextFromURL()</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1]: افتح اتصالًا مع مُحدِّد مورد موحَّد واحصل على مجرى دخل لقراءة البيانات منه.
	</li>
	<li>
		[2]: اِفحص فيما إذا كانت المحتويات من النوع النصي.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: ينبغي استدعاء التابع <code>connection.getContentType()‎</code> بعد استدعاء التابع <code>getInputStream()‎</code>
		</p>
	</div>
</blockquote>

<ul>
<li>
		[3]: اِنسَخ الأسطر النصية من مجرى الدخل إلى الشاشة حتى تصِل إلى نهاية الملف، أو حتى يقع خطأ.
	</li>
</ul>
<p>
	يَستخدِم البرنامج <a href="http://math.hws.edu/javanotes/source/chapter11/FetchURL.java" rel="external nofollow">FetchURL.java</a> البرنامج الفرعي المُعرَّف بالأعلى. بإمكانك تخصيص مُحدِّد المورد المطلوب أثناء تشغيل البرنامج من خلال سطر الأوامر؛ وإذا لم تُخصِّصه، فسيطلُب منك البرنامج تخصيصه. يَسمَح البرنامج بمُحدِّدات الموارد البادئة بكلمة "http://‎" أو "https://‎" إذا كان المُحدِّد يُشير إلى مورد بشبكة الإنترنت؛ أو تلك البادئة بكلمة "file://‎" إذا كان المُحدِّد يِشير إلى إحدى ملفات حاسوبك؛ أو تلك البادئة بكلمة "ftp://‎" إذا كان المُحدِّد يَستخدِم بروتوكول نقل الملفات File Transfer Protocol.
</p>

<p>
	إذا لم يبدأ بأي من تلك الكلمات، فسيُضيف كلمة "http://‎" تلقائيًا إلى بداية مُحدِّد المورد. يُمكِنك أن تُجرِّب مثلًا مُحدِّد المورد "math.hws.edu/javanotes" لاسترجاع الصفحة الرئيسية لهذا الكتاب من موقعه الإلكتروني، كما يُمكِنك تجريبه أيضًا مع بعض المُدْخَلات غير السليمة، لترى نوعية الأخطاء المختلفة التي قد تَحصُل عليها.
</p>

<h2>
	بروتوكول TCP/IP والخوادم والعملاء
</h2>

<p>
	يعتمد نقل المعلومات عبر شبكة الانترنت على بروتوكولين، هما بروتوكول التحكم بالنقل Transmission Control Protocol وبروتوكول الإنترنت Internet Protocol، ويُشار إليهما مجتمعين باسم TCP/IP. هناك أيضًا بروتوكولٌ أبسط يُسمَى UDP؛ حيث يُمكِن اِستخدَامه بدلًا من TCP بتطبيقاتٍ معينة، وهو مدعومٌ من قِبل جافا. ولكن سنكتفي هنا بمناقشة TCP/IP الذي يُوفِّر نقلًا موثوقًا ثنائي الاتجاه للمعلومات بين حواسيب الشبكات.
</p>

<p>
	لكي يتمكَّن برنامجان من تبادل المعلومات عبر <a href="https://academy.hsoub.com/devops/servers/%d8%aa%d8%b9%d8%b1%d9%81-%d8%b9%d9%84%d9%89-%d8%a8%d8%b1%d9%88%d8%aa%d9%88%d9%83%d9%88%d9%84-tcpip-%d9%88%d8%a8%d8%b9%d8%b6-%d9%85%d9%86-%d8%ae%d8%af%d9%85%d8%a7%d8%aa%d9%87-r169/" rel="">بروتوكول TCP/IP</a>، ينبغي أن يُنشِئ كلٌ منهما مقبسًا socket، كما ينبغي أن يكون المقبسان متصلين ببعضهما. تُنقَل المعلومات بينهما بعد إنشاء هذا الاتصال باستخدام مجاري تدفق streams دْخَل وخَرج. يَملُك كل برنامجٍ منهما مجريين للمُدخلات وللمُخرجات، ويَستطيع أحدهما مثلًا إرسال بعض البيانات إلى مجرى الخرج الخاص به، وستُنقَل إلى الحاسوب الآخر، ومنه إلى مجرى الدْخَل الخاص بالبرنامج الموجود على الطرف الآخر من الاتصال الشبكي. عندما يقرأ هذا البرنامج البيانات من مجرى الدْخَل الخاص به، يكون قد تَسلَّم بذلك البيانات التي أُرسلَت إليه عبر الشبكة.
</p>

<p>
	تَكْمُن الصعوبة في إنشاء الاتصال الشبكي ذاته من الأساس، حيث تنطوي العملية على مقبسين. يجب أن يُنشِئ أحد البرنامجين مقبسًا socket ويتركه ينتظر طلب اتصالٍ من المقبس الآخر، ويُقال أن المقبس المُنتظِر يَستمِع للاتصال. ويُنشِئ البرنامج الآخر من الجهة الأخرى من الاتصال المُفترَض مقبسًا آخرًا ليُرسِل طلب اتصالٍ إلى المقبس المُستمِع، والذي يَستجيب بدوره عندما يَتَسلّم طلب الاتصال، وبذلك يكون الاتصال قد اُنشِئ. بمجرد إنشاء الاتصال، يستطيع كل برنامج الحصول على مجريين للدخل والخرج لإرسال البيانات عبر الاتصال، ويستمر الاتصال بين البرنامجين من خلال تلك المجاري حتى يُقرِّر أحدهما إغلاقه.
</p>

<p>
	يُطلَق على البرنامج المُنشِئ لمقبس الاستماع listening socket اسم "الخادم server"؛ بينما يُطلَق على المقبس اسم "مقبس الخادم server socket". في المقابل، يُطلَق على البرنامج الذي يَتصِل بالخادم اسم "العميل client"؛ بينما يُطلَق على المقبس الذي يَستخدِمه لإجراء الاتصال اسم "مقبس العميل client socket". تكمن الفكرة ببساطة في أن الخادم يَقبُع بمكانٍ ما داخل الشبكة منتظرًا طلبات الاتصال من العملاء. يُمكِننا إذًا أن نفكر بالخادم وكأنه يُقدِم نوعًا معينًا من الخدمات، في حين يحاول العميل الوصول إلى تلك الخدمة عن طريق الاتصال بالخادم. يُعرَف ذلك باسم نموذج العميل / الخادم client/server model لنقل المعلومات عبر الشبكة.
</p>

<p>
	يستطيع الخادم أيضًا وبالكثير من التطبيقات أن يُوفِّر اتصالات لأكثر من عميلٍ واحدٍ بنفس الوقت؛ فعندما يَتصِّل عميلٌ معين بمقبس الاستماع الخاص بخادمٍ من هذا النوع، لا يتوقف المقبس عن الاستماع، وإنما يستمر بالاستماع إلى أي طلبات اتصالٍ إضافية بنفس الوقت الذي يَخدِّم خلاله العميل الأول. يتطلَّب ذلك استخدام الخيوط threads التي سنناقش طريقة عملها بالمقال التالي.
</p>

<p>
	يَستخدِم الصنف <code>URL</code> -الذي نُوقِش ببداية هذا المقال- مقبس عميلٍ لإجراء أي اتصالٍ ضروري عبر الشبكة؛ بينما يتواجد في الجهة الأخرى لذلك الاتصال برنامج خادم لاستقبال طلب الاتصال من كائن الصنف <code>URL</code>، ويقرأ طلبه بخصوص إحدى الملفات الموجودة بحاسوب الخادم، ثم يَستجيب للطلب بإرسال محتويات الملف المطلوب إلى ذلك الكائن عبر الشبكة. وأخيرًا، يُغلِق الخادم الاتصال بعد انتهائه من نقل البيانات.
</p>

<p>
	يجب أن يَجِد برنامج العميل طريقةً ما لتخصيص أي حاسوبٍ من ضمن كل تلك الحواسيب الموجودة بالشبكة يريد الاتصال به. في الحقيقة، يَملُك كل حاسوبٍ بشبكة الإنترنت عنوان IP يُميّزه عن غيره؛ كما يُمكِن الإشارة إلى كثيرٍ من الحواسيب باستخدام أسماء المجال domain names، مثل "math.hws.edu" أو "http://www.whitehouse.gov" (انظر مقال "<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D9%85%D8%A7-%D8%A8%D8%B9%D8%AF%D9%87-%D9%88%D8%B9%D9%84%D8%A7%D9%82%D8%AA%D9%87-%D8%A8%D8%AC%D8%A7%D9%81%D8%A7-r968/" rel="">الإنترنت وما بعده وعلاقته بجافا</a>").
</p>

<p>
	تتكوَّن عناوين IP (أو IPv4) التقليدية من أعدادٍ صحيحةٍ من "32 بت"، وتُكْتَب عادةً بصيغةٍ عشريةٍ مُنقطَّة، مثل "64.89.144.237"؛ بحيث يُمثِّل كل عددٍ من الأعداد الأربعة ضمن ذلك العنوان عددًا صحيحًا من "8 بتات" بنطاقٍ يتراوح من "0" إلى "255". يتوفَّر الآن إصدارٌ أحدث من بروتوكول الإنترنت هو IPv6؛ حيث تتكوَّن عناوينه من أعدادٍ صحيحة من "128 بت"، وتُكْتَب عادةً بصيغةٍ ست عشريّة hexadecimal، ويَستخدِم نقطتين وربما بعض المعلومات الإضافية الآخرى. ما يزال الإصدار الأحدث IPv6 نادرًا من الناحية العملية.
</p>

<p>
	يُمكِن لأي حاسوب أن يمتلك مجموعةً من عناوين IP، كما قد يمتلك عناوين IPv4 و IPv6، والتي يُعرَف إحداها عادةً باسم "عنوان الاسترجاع loopback"؛ حيث تستخدِم البرامج عنوان الاسترجاع، إذا كانت تريد التواصل مع برامجٍ أخرى على نفس الحاسوب. يَملُك عنوان الاسترجاع عنوان IPv4‎ هو ‏"127.0.0.1"، ويُمكِننا الإشارة إليه باستخدام اسم المجال "localhost".
</p>

<p>
	بالإضافة إلى ذلك، قد يكون هناك عناوين IP أخرى مرتبطة باتصالٍ شبكي مادي، كما يحتوي عادةً أي حاسوب على أداةٍ لعرض عناوين IP الموجودة به. كتب المؤلف البرنامج <a href="http://math.hws.edu/javanotes/source/chapter11/ShowMyNetwork.java" rel="external nofollow">ShowMyNetwork.java</a> ليَفعَل الشيء نفسه، وحَصَل على الخرج التالي بعد أن شغَّله على حاسوبه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_20" style="">
<span class="pln">   en1 </span><span class="pun">:</span><span class="pln">  </span><span class="pun">/</span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">1.47</span><span class="pln">  </span><span class="pun">/</span><span class="pln">fe80</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">211</span><span class="pun">:</span><span class="lit">24ff</span><span class="pun">:</span><span class="pln">fe9c</span><span class="pun">:</span><span class="lit">5271</span><span class="pun">%</span><span class="lit">5</span><span class="pln">  
   lo0 </span><span class="pun">:</span><span class="pln">  </span><span class="pun">/</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln">  </span><span class="pun">/</span><span class="pln">fe80</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">1</span><span class="pun">%</span><span class="lit">1</span><span class="pln">  </span><span class="pun">/</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">:</span><span class="lit">1</span><span class="pun">%</span><span class="lit">0</span></pre>

<p>
	تُشيِر أول كلمة بكل سطرٍ منهما إلى اسم بطاقة الشبكة network interface، والتي يَفهَم معناها نظام التشغيل فقط، كما يحتوي كل سطرٍ على عناوين IP الخاصة بالبطاقة؛ حيث تُشير بطاقة "lo0" على سبيل المثال إلى عنوان الاسترجاع loopback الذي يَملُك عادةً عنوان IPv4‏ هو "127.0.0.1". في الحقيقة، إن العدد "192.168.1.47" هو الأكثر أهميةً من بين كل تلك الأعداد؛ فهو يُمثِّل عنوان IPv4 المُستخدَم للاتصالات عبر الشبكة؛ أما الأعداد الآخرى فهي عناوين IPv6. ملاحظة: لا تُعدّ الخطوط المائلة ببداية كل عنوان جزءًا فعليًا منه.
</p>

<p>
	قد يحتوي الحاسوب على عدّة برامجٍ تُجرِي اتصالات شبكية بنفس الوقت، أو على برنامجٍ واحد يتبادل المعلومات مع مجموعةٍ من الحواسيب الأخرى؛ حيث يملك كلُّ اتصالٍ شبكي رقم مَنفَذ port number إلى جانب عنوان IP. يتكوَّن رقم المَنفَذ ببساطة من عددٍ صحيحٍ موجبٍ من "16 بت". لا يَستمِع الخادم إلى الاتصالات في العموم، وإنما يَستمِع إلى الاتصالات الواقعة برقم منفذٍ معين، ولذلك يجب أن يَعرِف أي عميلٍ مُحتمَل لخادمٍ معين كُلًا من عنوان الإنترنت (أو اسم المجال) الخاص بالحاسوب الذي يَعمَل عليه ذلك الخادم، وكذلك رقم المَنفَذ الذي يَستمِع إليه الخادم.
</p>

<p>
	تَستمِع خوادم الإنترنت عمومًا إلى الاتصالات الواقعة برقم المنفذ "80"، كما تحدث بعض خدمات الويب القياسية الأخرى بأرقام منافذٍ قياسيةٍ أخرى. تُعدّ أرقام المنافذ الأقل من "1024" ضمن أرقام المنافذ القياسية، وهي في الواقع محجوزةٌ لخدماتٍ معينة، ولذلك إذا أنشأت خادمك الخاص، ينبغي أن تَستخدِم رقم منفذٍ أكبر من "1024".
</p>

<h2>
	المقابس والصنف Socket
</h2>

<p>
	تُوفِّر حزمة <code>java.net</code> الصنفين <code>ServerSocket</code> و <code>Socket</code> لتنفيذ اتصالات بروتوكول TCP/IP؛ حيث يُمثِل كائنٌ من الصنف <code>ServerSocket</code> مقبس استماع listening socket ينتظر طلبات الاتصال من العملاء؛ بينما يُمثِّل كائنٌ من الصنف <code>Socket</code> طرفًا واحدًا من اتصالٍ فعلي، حيث يمكن أن يُمثِّل عميلًا قد أرسل طلبًا إلى خادم.
</p>

<p>
	يستطيع الخادم إنشاء كائنٍ من هذا الصنف -أي <code>Socket</code>-، ويطلب منه معالجة طلب اتصالٍ من عميلٍ معين، ويَسمَح ذلك للخادم بإنشاء عدة كائناتٍ من ذلك الصنف لمعالجة اتصالات عديدة. في المقابل، لا تُشارِك كائنات الصنف <code>ServerSocket</code> بالاتصالات، فهي فقط تستمع إلى طلبات الاتصال، وُتنشِئ كائناتٍ من الصنف <code>Socket</code> لمعالجة الاتصالات الفعلية.
</p>

<p>
	عندما تُنشِئ كائنًا من الصنف <code>ServerSocket</code>، عليك أن تُخصِّص رقم المَنفَذ port number الذي سيستمع إليه الخادم. انظر الباني constructor الخاص بهذا الصنف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_22" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> port</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">IOException</span></pre>

<p>
	يجب أن يقع رقم المَنفَذ ضمن نطاقٍ يتراوح من "0" إلى "65535"، كما يجب أن يكون أكبر من "1024". يُبلِّغ الباني عن استثناء من النوع <code>SecurityException</code>، إذا كان رقم المَنفَذ المُخصَّص أقل من "1024"؛ كما يُبلِّغ عن استثناء من النوع <code>IOException</code>، إذا كان رقم المَنفَذ المُحدَّد مُستخدَمًا بالفعل. يُمكِنك مع ذلك تمرير القيمة "0" مثل معاملٍ للتابع لتخبره بأن الخادم بإمكانه الاستماع إلى أي رقم مَنفَذٍ متاح.
</p>

<p>
	بمجرّد إنشاء كائنٍ من الصنف <code>ServerSocket</code>، فسيبدأ بالاستماع إلى طلبات الاتصال من العملاء. يَستقبِل التابع <code>accept()‎</code> المُعرَّف بالصنف <code>ServerSocket</code> طلبًا، ثم يُنشِئ اتصالًا مع العميل، ويعيد كائنًا من النوع <code>Socket</code> يُمكِن اِستخدَامه للاتصال مع العميل. يُعرَّف التابع <code>accept()‎</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_24" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="typ">Socket</span><span class="pln"> accept</span><span class="pun">()</span><span class="pln"> throws </span><span class="typ">IOException</span></pre>

<p>
	عندما تَستدعِي التابع <code>accept()‎</code>، فإنه لا يعيد قيمته حتى يتسلَّم طلب اتصال، أو حتى يقع خطأ ما، ولذلك يُعدّ تابعًا مُعطِّلًا block أثناء انتظاره للطلب؛ لأن البرنامج -أو بتعبير أدق الخيط thread الذي اِستدعَى التابع- لا يستطيع فعل ذلك بأي شيءٍ آخر، بينما تستطيع الخيوط الآخرى أن تُكمِل عملها بصورةٍ طبيعية. يُمكِنك استدعاء <code>accept()‎</code> مرةً بعد أخرى لتَستقبِل عدة طلبات اتصال، وسيستمر كائن الصنف <code>ServerSocket</code> بالاستماع إلى طلبات الاتصال إلى أن يُغلَق باستدعاء تابعه <code>close()‎</code>، أو إلى أن يَحدُث خطأ، أو أن ينتهي البرنامج بطريقةٍ ما.
</p>

<p>
	لنفترض أننا نريد إنشاء خادمٍ يَستمِع إلى رقم المَنفَذ "1728"، ويستمر باستقبال طلبات الاتصال طوال فترة تشغيل البرنامج. إذا كان <code>provideService(Socket)‎</code> تابعًا مسؤولًا عن معالجة اتصالٍ مع عميلٍ واحد، يُمكِننا أن نَكْتُب برنامج الخادم التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_26" style="">
<span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">ServerSocket</span><span class="pln"> server </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="lit">1728</span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">Socket</span><span class="pln"> connection </span><span class="pun">=</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln">
      provideService</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Server shut down with error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	من جهة العميل، يُمكِننا إنشاء كائنٍ من النوع <code>Socket</code> باستخدام أحد البُناة constructors المُعرَّفة بالصنف <code>Socket</code>. يُمكِننا استخدام الباني التالي لنتمكَّن من الاتصال بخادمٍ معين نَعرِف الحاسوب الذي يَعمَل عليه وكذلك رقم المَنفَذ الذي يَستمِع إليه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_28" style="">
<span class="kwd">public</span><span class="pln"> </span><span class="typ">Socket</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> computer</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> port</span><span class="pun">)</span><span class="pln"> throws </span><span class="typ">IOException</span></pre>

<p>
	بإمكاننا تمرير اسم المجال domain name أو عنوان IP على أنه قيمةٌ للمعامل الأول بالباني السابق. سيُعطِّل block هذا الباني التنفيذ حتى يُنشَئ الاتصال أو حتى يَحدُث خطأً.
</p>

<p>
	إذا كان لدينا مقبسٌ مُتصِلٌ بغض النظر عن طريقة إنشائه، يُمكِننا استدعاء أيٍّ من توابع الصنف <code>Socket</code>، مثل التابعين <code>getInputStream()‎</code> و <code>getOutputStream()‎</code> الذين يعيدان كائناتٍ من النوع <code>InputStream</code> و <code>OutputStream</code> على الترتيب، وبذلك، نكون قد حَصلنا على مجاري تدفق بإمكاننا اِستخدَامها لنقل المعلومات عبر هذا الاتصال. تُوضِح الشيفرة التالية الخطوط العريضة لتابع يُجرِي اتصالًا من طرف العميل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_30" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">void</span><span class="pln"> doClientConnection</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> computerName</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> serverPort</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">;</span><span class="pln">
   </span><span class="typ">InputStream</span><span class="pln"> in</span><span class="pun">;</span><span class="pln">
   </span><span class="typ">OutputStream</span><span class="pln"> out</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      connection </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Socket</span><span class="pun">(</span><span class="pln">computerName</span><span class="pun">,</span><span class="pln">serverPort</span><span class="pun">);</span><span class="pln">
      in </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">();</span><span class="pln">
      out </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">getOutputStream</span><span class="pun">();</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">
          </span><span class="str">"Attempt to create connection failed with error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">  </span><span class="com">// ‫استخدم المجريين in و out لتبادل المعلومات مع الخادم</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
           </span><span class="com">// قد تعتمد على الخادم لغلق الاتصال بدلًا من غلقه بنفسك</span><span class="pln">

   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">  </span><span class="com">// end doClientConnection()</span></pre>

<p>
	[1] افتح اتصالًا مع الحاسوب ورقم المَنفَذ المُخصَّصين للخادم، ثم انقل المعلومات عبر الاتصال.
</p>

<p>
	تُسهِّل كل تلك الأصناف من التعقيدات الكثيرة التي ينطوي عليها نقل المعلومات عبر الشبكات؛ أما إذا كنت تَجِد هذا صعبًا بالفعل، فإنه إذًا أكثر صعوبة. إذا كانت الشبكات جديرةً بالثقة كليًا، فلربما كان الأمر بنفس السهولة التي وُصِفت بها هنا، ولكنها ليست كذلك؛ ولهذا ينبغي كتابة برامج الشبكات بصورةٍ متينة robust تُمكِّنها من التعامل مع أخطاء الشبكات والبشر، ولكننا لن نناقش هذا هنا. لقد شرحنا الأفكار الأساسية لبرمجة الشبكات عمومًا، وسنكتفي بما تعرَّضنا له من تطبيقاتٍ بسيطة. لنفحص الآن أمثلةً قليلة على برمجة الخادم / العميل.
</p>

<h2>
	برنامج عميل/خادم بسيط
</h2>

<p>
	يتكوَّن المثال الأول من برنامجين تتوفَّر شيفرتهما بالملفين <a href="http://math.hws.edu/javanotes/source/chapter11/DateClient.java" rel="external nofollow">DateClient.java</a> و <a href="http://math.hws.edu/javanotes/source/chapter11/DateServer.java" rel="external nofollow">DateServer.java</a>؛ حيث يُمثِّل الأول عميلًا شبكيًا network client بسيطًا؛ بينما يُمثِّل الآخر خادمًا server. يُنشِئ العميل اتصالًا مع الخادم، ويقرأ سطرًا نصيًا واحدًا منه، ثم يَعرِضه على الشاشة؛ حيث يتكوَّن هذا السطر من التاريخ والتوقيت الحالي بالحاسوب الذي يَعمَل عليه الخادم. يجب بالطبع أن يَعرِف العميل أي حاسوبٍ يَعمَل عليه الخادم، وكذلك رقم المَنفَذ port الذي يَستمِع إليه الخادم حتى يتمكَّن من إنشاء اتصالٍ معه.
</p>

<p>
	يُمكِن أن يقع رقم المنفذ بين "1025" و "65535" عمومًا (الأرقام الواقعة بين "1" و "1024" محجوزةٌ للخدمات القياسية، ولا ينبغي استخدامها للخوادم الأخرى)، ولا يُحدِِث ذلك أي فرقٍ بشرط أن يَستخدِم الخادم والعميل نفس رقم المنفذ، ولنفترض أن الخادم بهذا المثال يَستمِع إلى رقم المنفذ "32007". يُمكِنك تمرير اسم أو عنوان IP الخاص بحاسوب الخادم مثل وسيط سطر أوامر للبرنامج <code>DateClient</code> أثناء تشغيله؛ فإذا كان الخادم مثلًا مُشغَّلًا على حاسوبٍ اسمه "math.hws.edu"، يُمكِنك أن تُشغِّل العميل باستخدام الأمر <code>java DateClient math.hws.edu</code>. في حالة عدم تخصيص حاسوب الخادم على أنه وسيط بسطر الأوامر، سيطلب البرنامج منك أن تُدْخِله. انظر الشيفرة الكاملة لبرنامج العميل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_32" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">net</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Scanner</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">DateClient</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> final </span><span class="typ">int</span><span class="pln"> LISTENING_PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">32007</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="typ">String</span><span class="pln"> hostName</span><span class="pun">;</span><span class="pln">         </span><span class="com">// اسم حاسوب الخادم</span><span class="pln">
        </span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">;</span><span class="pln">       </span><span class="com">// رقم منفذ الاتصال مع الخادم</span><span class="pln">
        </span><span class="typ">BufferedReader</span><span class="pln"> incoming</span><span class="pun">;</span><span class="pln"> </span><span class="com">// لقراءة البيانات من الاتصال</span><span class="pln">

        </span><span class="com">// اقرأ حاسوب الخادم من سطر الأوامر</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">args</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
            hostName </span><span class="pun">=</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Scanner</span><span class="pln"> stdin </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="typ">System</span><span class="pun">.</span><span class="pln">in</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"Enter computer name or IP address: "</span><span class="pun">);</span><span class="pln">
            hostName </span><span class="pun">=</span><span class="pln"> stdin</span><span class="pun">.</span><span class="pln">nextLine</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">// أجرِ الاتصال ثم اقرأ سطرًا نصيًا واعرضه</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            connection </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Socket</span><span class="pun">(</span><span class="pln"> hostName</span><span class="pun">,</span><span class="pln"> LISTENING_PORT </span><span class="pun">);</span><span class="pln">
            incoming </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedReader</span><span class="pun">(</span><span class="pln"> 
                             </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">InputStreamReader</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">())</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            </span><span class="typ">String</span><span class="pln"> lineFromServer </span><span class="pun">=</span><span class="pln"> incoming</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lineFromServer </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    </span><span class="com">// 2</span><span class="pln">
                </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"Connection was opened, "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> 
                        </span><span class="str">"but server did not send any data."</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">();</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">lineFromServer</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">();</span><span class="pln">
            incoming</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end main()</span><span class="pln">


</span><span class="pun">}</span><span class="pln"> </span><span class="com">//end class DateClient</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1]: يَفتَح هذا البرنامج اتصالًا مع الحاسوب المُخصَّص مثل وسيطٍ أول بسطر الأوامر؛ وإذا لم يكن مخصَّصًا بعد، اطلب من المُستخدِم أن يُدْخِل الحاسوب الذي يرغب في الاتصال به. يُجرى البرنامج الاتصال على رقم المنفذ <code>LISTENING_PORT</code>، ويقرأ سطرًا نصيًا واحدًا من الاتصال ثم يُغلق الاتصال، ويُرسِل أخيرًا النص المقروء إلى الخرج القياسي. الهدف من هذا البرنامج هو استخدامه مع البرنامج <code>DateServer</code> الذي يُرسِل كُلًا من التوقيت والتاريخ الحاليين بالحاسوب الذي يَعمَل عليه الخادم.
	</li>
	<li>
		[2]: أعاد التابع <code>incoming.readLine()‎</code> القيمة الفارغة مما يُعدّ إشارةً إلى وصوله إلى نهاية المجرى.
	</li>
</ul>
<p>
	لاحِظ أن الشيفرة المسؤولة عن الاتصال مع الخادم مُضمَّنةٌ داخل تعليمة <code>try..catch</code>، حتى تَلتقِط استثناءات النوع <code>IOException</code>، والتي يُحتمَل وقوعها أثناء فتح الاتصال أو غلقه أو قراءة البيانات من مجرى الدْخَل. أحطنا مجرى الدْخَل الخاص بالاتصال بكائن من النوع <code>BufferedReader</code>، والذي يحتوي على التابع <code>readLine()‎</code>؛ وهذا يُسهّل قراءة سطرٍ واحدٍ من المُدْخَلات. إذا أعاد التابع القيمة <code>null</code>، يكون الخادم قد أغلق الاتصال دون أن يُرسِل أي بيانات.
</p>

<p>
	حتى يَعمَل هذا البرنامج دون أخطاء، ينبغي أن تُشغِّل برنامج الخادم أولًا على الحاسوب الذي يُحاوِل العميل الاتصال به، حيث يُمكِنك أن تُشغِّل برنامجي العميل والخادم على نفس الحاسوب. على سبيل المثال، افتح نافذتي سطر أوامر، ثم شغِّل الخادم بإحداها، وشغِّل العميل بالنافذة الأخرى. علاوةً على ذلك، تَستطِيع أغلب الحواسيب استخدام اسم المجال "localhost" وعنوان IP التالي "127.0.0.1" للإشارة الى ذاتها، ولهذا يُمكِنك استخدام الأمر <code>java DateClient localhost</code> لتطلب من البرنامج <code>DateClient</code> الاتصال مع الخادم المُشغَّل على نفس الحاسوب؛ وإذا لم يَنجَح معك الأمر، جرِّب الأمر <code>java DateClient 127.0.0.1</code>.
</p>

<p>
	أطلقنا اسم <code>DateServer</code> على برنامج الخادم المقابل لبرنامج العميل <code>DataClient</code>، حيث يُنشِئ برنامج <code>DateServer</code> مقبسًا socket من النوع <code>ServerSocket</code> للاستماع إلى طلبات الاتصال برقم المنفذ "32007". ويَستمِر بعد ذلك بتنفيذ حلقة تكرار loop لا نهائية تَستقبِل طلبات الاتصال وتُعالِجها. تستمر حلقة التكرار بالعمل حتى ينتهي البرنامج بطريقةٍ ما، مثل كتابة <code>CONTROL-C</code> بنافذة سطر الأوامر التي شغَّلت الخادم منها.
</p>

<p>
	عندما يَستقبِل الخادم طلب اتصالٍ من عميلٍ معين، فإنه يَستدعِي برنامجًا فرعيًا لمعالجة هذا الاتصال، حيث يلتقط البرنامج الفرعي أي استثناءات من النوع <code>Exception</code> حتى لا ينهار الخادم، وهذا أمرٌ منطقي؛ فلا ينبغي للخادم أن يُغلَق لمجرد أن اتصالًا واحدًا مع عميل معين قد فشل لسببٍ ما؛ فقد يكون العميل هو سبب الخطأ أساسًا. بخلاف التقاطه للاستثناءات، فإنه يُنشِئ كائنًا من النوع <code>PrintWriter</code> لإرسال البيانات عبر الاتصال، ويُرسِل تحديدًا التاريخ والتوقيت الحالي إلى ذلك المجرى، ثم يُغلِق الاتصال؛ حيث يَستخدِم البرنامج الصنف القياسي <code>java.util.Date</code> للحصول على التوقيت الحالي، وتُمثِل كائنات الصنف <code>Date</code> تاريخًا وتوقيتًا محددًا، ويُنشِئ الباني الافتراضي <code>new Date()‎</code> كائنًا يُمثِّل توقيت إنشائه. انظر الشيفرة الكاملة لبرنامج الخادم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_34" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">net</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">DateServer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> final </span><span class="typ">int</span><span class="pln"> LISTENING_PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">32007</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="typ">ServerSocket</span><span class="pln"> listener</span><span class="pun">;</span><span class="pln">  </span><span class="com">// يستمع إلى طلبات الاتصال</span><span class="pln">
        </span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">;</span><span class="pln">      </span><span class="com">// للتواصل مع البرنامج المتصل</span><span class="pln">

        </span><span class="com">// 2</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            listener </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="pln">LISTENING_PORT</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Listening on port "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> LISTENING_PORT</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    </span><span class="com">// استقبل طلب الاتصال التالي وعالِجه</span><span class="pln">
                connection </span><span class="pun">=</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln"> 
                sendDate</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Sorry, the server has shut down."</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end main()</span><span class="pln">


    </span><span class="com">// 3</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> sendDate</span><span class="pun">(</span><span class="typ">Socket</span><span class="pln"> client</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Connection from "</span><span class="pln"> </span><span class="pun">+</span><span class="pln">  
                    client</span><span class="pun">.</span><span class="pln">getInetAddress</span><span class="pun">().</span><span class="pln">toString</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            </span><span class="typ">Date</span><span class="pln"> now </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">();</span><span class="pln">  </span><span class="com">// التوقيت والتاريخ الحالي</span><span class="pln">
            </span><span class="typ">PrintWriter</span><span class="pln"> outgoing</span><span class="pun">;</span><span class="pln">   </span><span class="com">// مجرى لإرسال البيانات</span><span class="pln">
            outgoing </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">getOutputStream</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            outgoing</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> now</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            outgoing</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">  </span><span class="com">// تأكّد من إرسال البيانات</span><span class="pln">
            client</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">){</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="com">// end sendDate()</span><span class="pln">


</span><span class="pun">}</span><span class="pln"> </span><span class="com">//end class DateServer</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1] يُمثِّل هذا البرنامج خادمًا يستمع إلى طلبات الاتصال على رقم المَنفَذ المخصَّص بواسطة الثابت <code>LISTENING_PORT</code>. عند فتح اتصال، يُرسِل البرنامج التوقيت الحالي إلى المقبس المُتصِل، ويستمر البرنامج في تسلُّم طلبات الاتصال ومعالجتها حتى يُغلِق عن طريق الضغط على <code>CONTROL-C</code> على سبيل المثال. ملاحظة: يعالِج الخادم طلبات الاتصال عند وصولها بدلًا من إنشاء خيطٍ thread منفصلٍ لمعالجتها.
	</li>
	<li>
		[2] اِستقبِل طلبات الاتصال وعالِجها للأبد أو إلى حين وقوع خطأ. ملاحظة: يلتقط البرنامج <code>sendDate()‎</code> الأخطاء الواقعة أثناء نقل البيانات مع برنامجٍ مُتصِل ويعالجها حتى لا ينهار الخادم.
	</li>
	<li>
		[3] يُمثِّل المعامل <code>client</code> مقبسًا متصلًا بالفعل مع برنامجٍ آخر، لذلك احصل على مجرى خرج لهذا الاتصال، وأرسل إليه التوقيت الحالي، ثم أغلق الاتصال.
	</li>
</ul>
<p>
	عندما تُشغِّل البرنامج <code>DateServer</code> بواجهة سطر الأوامر، فسيستمر بانتظار طلبات الاتصال ويُبلِّغ عنها عندما يتسلّمها. في المقابل، إذا أردت إتاحته على الحاسوب باستمرار، فينبغي أن تُشغِّله مثل <strong>عفريت daemon</strong> (وهو أمرٌ لن نناقش طريقة تنفيذه هنا)؛ وهو مثل برنامجٍ يَعمَل خفيةً وباستمرار على الحاسوب بغض النظر عن المُستخدِم. يُمكِنك ضبط الحاسوب ليبدأ بتشغيل هذا البرنامج تلقائيًا بمجرد أن تُشغِّله -أي الحاسوب-، وسيَعمَل بذلك البرنامج بالخلفية حتى إذا كنت تَستخدِم الحاسوب لأغراضٍ أخرى.
</p>

<p>
	تُشغِّل الحواسيب المسؤولة عن إتاحة بعض الصفحات على شبكة الويب العالمية مثلًا عفريتًا يستمع إلى طلبات العملاء لتلك الصفحات ثم ينقلها إليهم، ويشبه ذلك البرنامج <code>DateServer</code>، كما يُمكِن تشغيله يدويًا ببساطة لاختباره؛ فكل تلك الأمثلة بالنهاية غير متينة كفاية ولا تتمتع بخاصياتٍ مكتملة كفاية لتشغيلها خوادمًا فعلًا. بالمناسبة، تُعدّ كلمة "daemon" تهجئةً مختلفةً لكلمة "demon" وتُنطَق بنفس الطريقة.
</p>

<p>
	ملاحظة: بعد أن يَستدعِي البرنامج التابع <code>outgoing.println()‎</code> لإرسال سطرٍ واحدٍ من البيانات إلى العميل؛ يَستدعِي أيضًا التابع <code>outgoing.flush()‎</code> المُتاح بجميع أصناف مجاري الخرج، ليتأكَّد من إرسال البيانات التي كتبها بالمجرى بالفعل إلى مقصدها. في العموم، عليك استدعاء تلك الدالة بكل مرةٍ تَستخدِم بها مجرى خرج لإرسال بياناتٍ عبر اتصالٍ شبكي؛ لأنك إن لم تَفعَل ذلك، فقد يستمر المجرى بتخزين البيانات إلى أن يَصِل حجمها إلى القدر الكافي، ثم يُرسِلها بهدف رفع الكفاءة، ولكنه قد يؤدي إلى بعض التأخير غير المقبول إذا كان العميل ينتظر الرد، بل ربما حتى لا تُرسَل بعض البيانات عند غلق المقبس socket؛ ولذلك من الضروري استدعاء <code>flush()‎</code> قبل غلق أي اتصال. يُعدّ هذا واحدًا من الحالات التي قد تتصرف خلاله تنفيذات جافا المختلفة بطرائقٍ مختلفة. في العموم، إذا لم تستدعي التابع <code>flush</code> مع مجاري تدفق الخرج، فلربما سيَعمَل تطبيقك على بعض أنواع الحواسيب ولكنه قد لا يَعمَل أيضًا على بعضها الآخر.
</p>

<h2>
	برنامج محادثة عبر الشبكة
</h2>

<p>
	كان الخادم بالبرنامج السابق <code>DateServer</code> يُرسِل المعلومات إلى العميل ليقرأها. في الواقع، يُمكِننا أيضًا إنشاء اتصالٍ ثنائي الاتجاه بين العميل والخادم. سنفحص أولًا برنامجًا بسيطًا مُكوَّنًا من عميل وخادم؛ حيث سيُمكِّن البرنامج المُستخدِمين بطرفي الاتصال من إرسال الرسائل إلى بعضهما بعضًا. يَعمَل البرنامج ببيئة سطر الأوامر command-line environment، حيث يستطيع كل مُستخدِم كتابة رسائله، وسينتظر الخادم -بهذا المثال- طلب اتصالٍ من عميلٍ واحد فقط، ثم سيتوقَّف عن الاستماع إلى أي طلبات اتصال جديدة؛ أي لن يتمكَّن أي عميلٍ آخر غير العميل الأول من الاتصال بالخادم.
</p>

<p>
	بعد أن يَتصِل الخادم والعميل معًا، سيَعمَل البرنامج بكلا الطرفين بنفس الطريقة تقريبًا. سيَكتُب المُستخدِم بطرف العميل رسالةً ويُرسِلها إلى الخادم الذي سيَعرِضها بدوره إلى المُستخدِم بطرف الخادم، ثم سيَكْتُب المُستخدِم بطرف الخادم رسالةً لتنتقل بعدها إلى العميل الذي يُمكِنه كتابة رسالةٍ أخرى، وهكذا. سيستمر الأمر إلى أن يُقرِّر أي مُستخدِمٍ منهما كتابة كلمة "quit" برسالة؛ وعندما يحدث ذلك، يُغلَق الاتصال وينتهي البرنامج بكلا الطرفين.
</p>

<p>
	يتشابه برنامجا الخادم والعميل إلى حدٍ كبير، بينما يختلفان فقط بطريقة فتح الاتصال؛ فبرنامج العميل مُصمَّم ليُرسِل أول رسالةٍ؛ بينما يُصمَّم الخادم لاستقبالها. يُمكِنك الإطلاع على برنامجي الخادم والعميل بالملفين <a href="http://math.hws.edu/javanotes/source/chapter11/CLChatClient.java" rel="external nofollow">CLChatClient.java</a> و <a href="http://math.hws.edu/javanotes/source/chapter11/CLChatServer.java" rel="external nofollow">CLChatServer.java</a>، حيث يُشير الاسم "CLChat" إلى اختصارٍ لعبارة "command-line chat"، أي محادثة عبر سطر الأوامر. تَعرِض الشيفرة التالية برنامج الخادم (برنامج العميل مشابه):
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5050_36" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">net</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Scanner</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">


</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CLChatServer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="com">// رقم المنفذ الافتراضي الذي ينبيغ الاستماع إليه إذا لم يُخصِّصه المُستخدم</span><span class="pln">
    </span><span class="kwd">static</span><span class="pln"> final </span><span class="typ">int</span><span class="pln"> DEFAULT_PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1728</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// 2</span><span class="pln">

    </span><span class="kwd">static</span><span class="pln"> final </span><span class="typ">String</span><span class="pln"> HANDSHAKE </span><span class="pun">=</span><span class="pln"> </span><span class="str">"CLChat"</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// يَسبِق هذا المحرف جميع الرسائل المُرسَلة</span><span class="pln">
    </span><span class="kwd">static</span><span class="pln"> final </span><span class="kwd">char</span><span class="pln"> MESSAGE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'0'</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// يُرسَل هذا المحرف إلى البرنامج المتصل عندما يغلق المستخدم الاتصال</span><span class="pln">
    </span><span class="kwd">static</span><span class="pln"> final </span><span class="kwd">char</span><span class="pln"> CLOSE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'1'</span><span class="pun">;</span><span class="pln">


    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="typ">int</span><span class="pln"> port</span><span class="pun">;</span><span class="pln">   </span><span class="com">// رقم المنفذ الذي يستمع إليه الخادم</span><span class="pln">

        </span><span class="typ">ServerSocket</span><span class="pln"> listener</span><span class="pun">;</span><span class="pln">  </span><span class="com">// يستمع إلى طلبات الاتصال</span><span class="pln">
        </span><span class="typ">Socket</span><span class="pln"> connection</span><span class="pun">;</span><span class="pln">      </span><span class="com">// للتواصل مع العميل</span><span class="pln">

        </span><span class="typ">BufferedReader</span><span class="pln"> incoming</span><span class="pun">;</span><span class="pln">  </span><span class="com">// مجرى لاستقبال البيانات من العميل</span><span class="pln">

        </span><span class="typ">PrintWriter</span><span class="pln"> outgoing</span><span class="pun">;</span><span class="pln">     </span><span class="com">// مجرى لإرسال البيانات إلى العميل </span><span class="pln">
        </span><span class="typ">String</span><span class="pln"> messageOut</span><span class="pun">;</span><span class="pln">        </span><span class="com">// رسالة ينبغي إرسالها إلى العميل</span><span class="pln">
        </span><span class="typ">String</span><span class="pln"> messageIn</span><span class="pun">;</span><span class="pln">         </span><span class="com">// رسالة ينبغي استقبالها من العميل</span><span class="pln">

        </span><span class="com">// ‫مغلِّف للكائن System.in لقراءة أسطر مدخلة من المستخدم</span><span class="pln">
        </span><span class="typ">Scanner</span><span class="pln"> userInput</span><span class="pun">;</span><span class="pln">        

        </span><span class="com">// 3</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">args</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> 
            port </span><span class="pun">=</span><span class="pln"> DEFAULT_PORT</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                port</span><span class="pun">=</span><span class="pln"> </span><span class="typ">Integer</span><span class="pun">.</span><span class="pln">parseInt</span><span class="pun">(</span><span class="pln">args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">port </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> port </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">65535</span><span class="pun">)</span><span class="pln">
                    </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">NumberFormatException</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">NumberFormatException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Illegal port number, "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln">
                </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">// 4</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            listener </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ServerSocket</span><span class="pun">(</span><span class="pln">port</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Listening on port "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">getLocalPort</span><span class="pun">());</span><span class="pln">
            connection </span><span class="pun">=</span><span class="pln"> listener</span><span class="pun">.</span><span class="pln">accept</span><span class="pun">();</span><span class="pln">
            listener</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">  
            incoming </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedReader</span><span class="pun">(</span><span class="pln"> 
                    </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">InputStreamReader</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">getInputStream</span><span class="pun">())</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
            outgoing </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">getOutputStream</span><span class="pun">());</span><span class="pln">
            outgoing</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">HANDSHAKE</span><span class="pun">);</span><span class="pln">  </span><span class="com">// Send handshake to client.</span><span class="pln">
            outgoing</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">
            messageIn </span><span class="pun">=</span><span class="pln"> incoming</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">  </span><span class="com">// Receive handshake from client.</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln"> HANDSHAKE</span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="pln">messageIn</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"Connected program is not a CLChat!"</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Connected.  Waiting for the first message."</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"An error occurred while opening connection."</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">e</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">// 5</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            userInput </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="typ">System</span><span class="pun">.</span><span class="pln">in</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"NOTE: Enter 'quit' to end the program.\n"</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"WAITING..."</span><span class="pun">);</span><span class="pln">
                messageIn </span><span class="pun">=</span><span class="pln"> incoming</span><span class="pun">.</span><span class="pln">readLine</span><span class="pun">();</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">messageIn</span><span class="pun">.</span><span class="pln">length</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="com">// 6</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">messageIn</span><span class="pun">.</span><span class="pln">charAt</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> CLOSE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Connection closed at other end."</span><span class="pun">);</span><span class="pln">
                        connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
                        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    messageIn </span><span class="pun">=</span><span class="pln"> messageIn</span><span class="pun">.</span><span class="pln">substring</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"RECEIVED:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> messageIn</span><span class="pun">);</span><span class="pln">
                </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"SEND:      "</span><span class="pun">);</span><span class="pln">
                messageOut </span><span class="pun">=</span><span class="pln"> userInput</span><span class="pun">.</span><span class="pln">nextLine</span><span class="pun">();</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">messageOut</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"quit"</span><span class="pun">))</span><span class="pln">  </span><span class="pun">{</span><span class="pln">
                        </span><span class="com">// 7</span><span class="pln">
                    outgoing</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">CLOSE</span><span class="pun">);</span><span class="pln">
                    outgoing</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">  </span><span class="com">// تأكّد من إرسال البيانات</span><span class="pln">
                    connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
                    </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Connection closed."</span><span class="pun">);</span><span class="pln">
                    </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                outgoing</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">MESSAGE </span><span class="pun">+</span><span class="pln"> messageOut</span><span class="pun">);</span><span class="pln">
                outgoing</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln"> </span><span class="com">// تأكَّد من إرسال البيانات</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">outgoing</span><span class="pun">.</span><span class="pln">checkError</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"Error occurred while transmitting message."</span><span class="pun">);</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Sorry, an error has occurred.  Connection lost."</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end main()</span><span class="pln">



</span><span class="pun">}</span><span class="pln"> </span><span class="com">//end class CLChatServer</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1] يُمثِّل هذا البرنامج أحد طرفي برنامج محادثةٍ بسيط عبر سطر الأوامر، حيث يَعمَل البرنامج كأنه خادم ينتظر طلبات الاتصال من البرنامج <code>CLChatClient</code>. يُمكِن تخصيص رقم المنفذ الذي يستمع إليه الخادم مثل وسيط بسطر الأوامر؛ ويَستخدِم البرنامج في حالة عدم تخصيصه رقم المنفذ الافتراضي المُخصَّص عبر الثابت <code>DEFAULT_PORT</code>. ملاحظة: يستمع الخادم إلى أي رقم منفذٍ متاح، في حالة تخصيص العدد صفر رقمًا للمنفذ. يدعم هذا البرنامج اتصالًا واحد فقط؛ فبمجرد فتح الاتصال، يتوقف مقبس الاستماع، ويُرسِل طرفي الاتصال بعد ذلك رسالةً نصيةً لتحقيق الاتصال إلى بعضهما بعضًا، ليتأكّد كلا الطرفين من أن البرنامج على الطرف الآخر من النوع الصحيح، وفي تلك الحالة، يبدأ البرنامجان المتصلان بتبادل الرسائل. لا بُدّ أن يُرسِل برنامج العميل الرسالة الأولى، وبإمكان المُستخدِم بأيٍّ من الطرفين إغلاق الاتصال بإدخال السلسلة النصية "quit". ملاحظة: لا بُدّ أن يكون المحرف الأول بأي رسالةٍ نصيةٍ مُرسَلة عبر الشبكة مساويًا للقيمة "0" أو "1"، حيث يُفسَّر على أنه أمر.
	</li>
	<li>
		[2] سلسلة نصية لتحقيق الاتصال؛ حيث يرسل طرفي الاتصال تلك الرسالة النصية إلى بعضهما بمجرد فتح الاتصال لنتأكّد من أن الطرف الآخر هو برنامج <code>CLChat</code>.
	</li>
	<li>
		[3] اقرأ رقم المنفذ من سطر الأوامر أو اِستخدِم رقم المنفذ الافتراضي إذا لم يُخصِّصه المُستخدم.
	</li>
	<li>
		[4] انتظر طلب اتصال؛ وعندما يَصِل طلب اتصال، أغلق المستمع، وأنشِئ مجاري تدفق لتبادل البيانات والتحقق من الاتصال.
	</li>
	<li>
		[5] تبادل الرسائل مع الطرف الآخر من الاتصال حتى يُغلِق أحدهما الاتصال. ينتظر لخادم الرسالة الأولى من العميل، ويتبادل بعد ذلك الطرفان الرسائل جيئةً وذهابًا.
	</li>
	<li>
		[6] يعد المِحرف الأول من الرسالة أمرًا. إذا كان الأمر هو إغلاق الاتصال، أغلقه؛ أما إذا لم يَكن كذلك، اِحذِف محرف الأمر من الرسالة وأكمل المعالجة.
	</li>
	<li>
		[7] يرغب المُستخدِم بإغلاق الاتصال. بلِّغ الطرف الآخر وأغلق الاتصال.
	</li>
</ul>
<p>
	يُعدّ هذا البرنامج أكثر متانةً robust نوعًا ما من البرنامج <code>DateServer</code>؛ لأنه يُجرِي <strong>تحقيق اتصال handshake</strong> ليتأكّد من أن العميل الذي يحاول الاتصال به هو بالفعل البرنامج <code>CLChatClient</code>. يُجرَى تحقيق الاتصال بتبادل بعض المعلومات بين العميل والخادم على أنه جزءٌ من عملية إنشاء الاتصال قبل إرسال أي بياناتٍ فعلية، ويُرسِل طرفا الاتصال في تلك الحالة سلسلةً نصيةً إلى الطرف الآخر لتعريف هويته؛ حيث يُعدّ تحقيق الاتصال جزءًا من بروتوكول إجراء اتصال -أنشأه الكاتب- بين البرنامجين <code>CLChatClient</code> و<code>CLChatServer</code>.
</p>

<p>
	يُعدّ أي بروتوكول توصيفًا مفًصَّلًا لما يُمكِن تبادله من بياناتٍ ورسائلٍ عبر اتصالٍ معين، وكذلك طريقة تمثيل تلك البيانات، وبأي ترتيبٍ ينبغي إرسالها. ويُعدّ تصميم البروتوكول جانبًا مهمًا بتطبيقات الخوادم/العملاء. ينطوي بروتوكول "CLChat" على جانبٍ آخر بالإضافة إلى تحقيق الاتصال؛ حيث يَنُصّ على أن المحرف الأول بأي سطرٍ نصي يُرسَل عبر الاتصال هو أمر. إذا كان المِحرف الأول يُساوِي "0"، فيُمثِّل السطر رسالةً من مُستخدمٍ لآخر؛ أما إذا كان يُساوِي "1"، فسيُشير السطر إلى أن أحدهما قد أدخَل الأمر "quit"، مما يؤدي إلى غلق الاتصال.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: إذا أردت أن تُجرِّب هذا البرنامج على حاسوبٍ واحد، يُمكِنك استخدام نافذتي سطر أوامر. اُكتُب بإحداها الأمر <code>java CLChatServer</code> لتشغيل الخادم، واُكتُب الأمر <code>java CLChatClient localhost</code> بالأخرى للاتصال بالخادم المُشغَّل على نفس الحاسوب. إذا لم يَكُن الخادم مستمِعًا للمَنفَذ الافتراضي، يُمكِنك تخصيص رقم المَنفَذ خيارًا ثانيًا للبرنامج. لاحِظ أنه في حالة عدم تخصيص معلومات الاتصال بسطر الأوامر، فسيَطلُبها منك البرنامج.
		</p>
	</div>
</blockquote>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c11/s4.html" rel="external nofollow">Section 4: Networking</a> من فصل Chapter 11: Input/Output Streams, Files, and Networking من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1460/" rel="">معالجة الملفات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D9%85%D8%A7-%D8%A8%D8%B9%D8%AF%D9%87-%D9%88%D8%B9%D9%84%D8%A7%D9%82%D8%AA%D9%87-%D8%A8%D8%AC%D8%A7%D9%81%D8%A7-r968/" rel="">الإنترنت وما بعده وعلاقته بجافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r967/" rel="">واجهة المستخدم الحديثة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/certificates/cisco/ccna/%D9%81%D9%87%D9%85-%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D9%85%D8%B6%D9%8A%D9%81%D9%8A%D9%86-%D9%81%D9%8A-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-r4/" rel="">فهم نموذج التواصل بين المضيفين في الشبكات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1474</guid><pubDate>Wed, 16 Feb 2022 10:55:08 +0000</pubDate></item><item><title>&#x62A;&#x62D;&#x644;&#x64A;&#x644; &#x632;&#x645;&#x646; &#x62A;&#x634;&#x63A;&#x64A;&#x644; &#x627;&#x644;&#x62E;&#x631;&#x627;&#x626;&#x637; &#x627;&#x644;&#x645;&#x646;&#x641;&#x630;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x634;&#x62C;&#x631;&#x629; &#x628;&#x62D;&#x62B; &#x62B;&#x646;&#x627;&#x626;&#x64A;&#x629; TreeMap &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%AC%D8%B1%D8%A9-%D8%A8%D8%AD%D8%AB-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-treemap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1453/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61f79d3978717_--------.png.878e4e3d344d77dc59973250352613d3.png" /></p>
<p>
	سنناقش في هذه المقالة تنفيذًا جديدًا للواجهة <code>Map</code> يُعرَف باسم <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A3%D8%B4%D8%AC%D8%A7%D8%B1-%D8%A7%D9%84%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-binary-trees-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1404/" rel="">شجرة البحث الثنائية</a> binary search tree. يشيع استخدام هذا التنفيذ عند الحاجة إلى الاحتفاظ بترتيب العناصر.
</p>

<h2>
	ما هي مشكلة التعمية hashing؟
</h2>

<p>
	يُفترَض أن تكون على معرفةٍ بالواجهة <code>Map</code>، وبالصنف المُنفِّذ لها <code>HashMap</code> الذي تُوفِّره جافا. إذا كنت قد قرأت مقالة <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%A3%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashmap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1452/" rel="">تحسين أداء الخرائط المُنفَّذة باستخدام التعمية</a> التي نفّذنا بها نفس الواجهة باستخدام جدول hash table، فيُفترَض أنك تَعرِف الكيفية التي يَعمَل بها الصنف <code>HashMap</code>، والسببَ الذي لأجله تَستغرِق توابع ذلك التنفيذ زمنًا ثابتًا.
</p>

<p>
	يشيع استخدام الصنف <code>HashMap</code> بفضل كفاءته العالية، ولكنه مع ذلك ليس التنفيذ الوحيد للواجهة <code>M</code><code>ap</code>، فهناك أسبابٌ عديدةٌ قد تدفعك لاختيار تنفيذٍ آخرَ، منها:
</p>

<ol>
	<li>
		قد تستغرق عملية حساب شيفرة التعمية زمنًا طويلًا. فعلى الرغم من أن عمليات الصنف <code>HashMap</code> تستغرق زمنًا ثابتًا، فقد يكون ذلك الزمن كبيرًا.
	</li>
	<li>
		تَعمَل التعمية بشكلٍ جيّدٍ فقط عندما تُوزِّع دالةُ التعميةِ hash function المفاتيحَ بالتساوي على الخرائط الفرعية، ولكنّ تصميمَ دوالِّ التعمية لا يُعدّ أمرًا سهلًا، فإذا احتوت خريطةٌ فرعيّةٌ معيّنةٌ على مفاتيحَ كثيرةٍ، تقل كفاءة الصنف <code>HashMap</code>.
	</li>
	<li>
		لا تُخزَّن المفاتيح في الجدول وفقًا لترتيبٍ معيّنٍ، بل قد يتغير ترتيبها عند إعادة ضبط حجم الجدول وإعادة حساب شيفرات التعمية للمفاتيح. بالنسبة لبعض التطبيقات، قد يكون الحفاظ على ترتيب المفاتيح ضروريًا أو مفيدًا على الأقل.
	</li>
</ol>

<p>
	من الصعب حل كل تلك المشكلات في الوقت نفسه، ومع ذلك، تُوفِّر جافا التنفيذ <code>TreeMap</code> الذي يُعالِج بعضًا منها:
</p>

<ol>
	<li>
		لا يَستخدِم ذلك الصنفُ دالّةَ تعميةٍ، وبالتالي، يتجنَّب الزمن الإضافي اللازم لحساب شيفرات التعمية، كما يُجنّبُنا صعوباتِ اختيار دالّةِ تعميةٍ مناسبة.
	</li>
	<li>
		تُخزَّن المفاتيح في الصنف <code>TreeMap</code> بهيئة شجرةِ بحثٍ ثنائيّةٍ، مما يُسهِّل من اجتياز المفاتيح وفقًا لترتيبٍ معيّنٍ وبزمنٍ خطّي.
	</li>
	<li>
		يتناسب زمن تنفيذ غالبيّة توابع الصنف <code>TreeMap</code> مع log(n)‎، والتي رغم أنها ليست بكفاءة الزمن الثابت، ولكنها ما تزال جيدةً جدًا.
	</li>
</ol>

<p>
	سنشرح طريقة عمل أشجار البحث الثنائية في القسم التالي ثم سنستخدِمها لتنفيذ الواجهة <code>Map</code>، وأخيرًا، سنُحلّل أداء التوابعِ الأساسيّةِ في الخرائط المُنفَّذة باستخدام شجرة.
</p>

<h2>
	أشجار البحث الثنائية
</h2>

<p>
	شجرة البحث الثنائية عبارةٌ عن شجرةٍ تحتوي كلُّ عقدةٍ فيها على مفتاحٍ، كما تتوفّر فيها "خاصية BST" التي تنص على التالي:
</p>

<ol>
	<li>
		إذا كان لأي عقدةٍ أبٍ عقدةٌ ابنةٌ يسرى، فلا بُدّ أن تكون قيم جميع المفاتيح الموجودة في الشجرة الفرعية اليسرى أصغرَ من قيمة مفتاح تلك العقدة.
	</li>
	<li>
		إذا كان لأي عقدةٍ أبٍ عقدةٌ ابنةٌ يمنى، فلا بُدّ أن تكون قيم جميع المفاتيح الموجودة في الشجرة الفرعية اليمنى أكبرَ من قيمة مفتاح تلك العقدة.
	</li>
</ol>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91068" href="https://academy.hsoub.com/uploads/monthly_2022_01/001BinarySearchTree.PNG.d5b4f7cb0a7b54f6c3f8d086e194380c.PNG" rel="" data-fileext="PNG"><img alt="001BinarySearchTree.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="91068" data-unique="a8li1ua97" src="https://academy.hsoub.com/uploads/monthly_2022_01/001BinarySearchTree.PNG.d5b4f7cb0a7b54f6c3f8d086e194380c.PNG"></a>
</p>

<p>
	تَعرِض الصورة السابقة شجرة أعدادٍ صحيحةٍ تُحقِّق الشروطَ السابقة. هذه الصورة مأخوذةٌ من مقالةِ ويكيبيديا موضوعها <a href="https://wiki.hsoub.com/Algorithms/binary_search_trees" rel="external">أشجار البحث الثنائية</a>، والتي قد تفيدك لحل هذا التمرين.
</p>

<p>
	لاحِظ أن مفتاح عقدة الجذر يساوي 8. يُمكِنك التأكّد من أن مفاتيح العقد الموجودة على يسار عقدة الجذر أقلّ من 8 بينما مفاتيح العقد الموجودة على يمينها أكبرَ من 8. تأكّد من تحقّق نفس الشرط للعقد الأخرى.
</p>

<p>
	لا يَستغرِق البحث عن مفتاحٍ ما ضمن شجرةِ بحثٍ ثنائيّةٍ زمنًا طويلًا، لأنك غير مُضطرّ للبحث في كامل الشجرة، وإنما عليك أن تبدأ من جذر الشجرة، ومن ثمّ، تُطبِّق الخوارزميةَ التالية:
</p>

<ol>
	<li>
		افحص قيمة المفتاح الهدف<code>target</code> الذي تبحث عنه وطابقه مع قيمة مفتاح العقدة الحالية. فإذا كانا متساويين، فقد انتهيت بالفعل.
	</li>
	<li>
		أمّا إذا كان المفتاح <code>target</code> أصغرَ من المفتاح الحاليِّ، ابحث في الشجرة الموجودة على اليسار، فإذا لم تكن موجودةً، فهذا يَعنِي أن المفتاح <code>target</code> غيرُ موجودٍ في الشجرة.
	</li>
	<li>
		وأمّا إذا كان المفتاح <code>target</code> أكبرَ من المفتاح الحاليِّ، ابحث في الشجرة الموجودة على اليمين. فإذا لم تكن موجودة، فهذا يَعنِي أنّ المفتاح <code>target</code> غير موجود في الشجرة.
	</li>
</ol>

<p>
	يَعنِي ما سبق أنك مضطرٌّ للبحث في عقدةٍ ابنة واحدةٍ فقط لكل مستوىً ضمن الشجرة. فعلى سبيل المثال، إذا كنت تبحث عن مفتاح <code>target</code> قيمته تساوي 4 في الرسمة السابقة، فعليك أن تبدأ من عقدة الجذر التي تحتوي على المفتاح 8، ولأن المفتاح المطلوب أقلَّ من 8، فستذهب إلى اليسار، ولأنه أكبر من 3، فستذهب إلى اليمين، ولأنه أقل من 6، فستذهب إلى اليسار، ثم ستعثر على المفتاح الذي تبحث عنه.
</p>

<p>
	تطلّب البحث عن المفتاح في المثال السابق 4 عملياتِ موازنةٍ رغم أنّ الشجرة تحتوي على 9 مفاتيح. يتناسب عدد الموازنات المطلوبة في العموم مع ارتفاع الشجرة وليس مع عدد المفاتيح الموجودة فيها.
</p>

<p>
	ما الذي نستنتجه من ذلك بخصوص العلاقة بين ارتفاع الشجرة <code>h</code> وعدد العقد <code>n</code>؟ إذا بدأنا بارتفاعٍ قصيرٍ وزدناه تدريجيًّا، فسنحصل على التالي:
</p>

<ul>
	<li>
		إذا كان ارتفاع الشجرة <code>h</code> يساوي 1، فإن عدد العقد <code>n</code> ضمن تلك الشجرة يساوي 1.
	</li>
	<li>
		وإذا كان ارتفاع الشجرة <code>h</code> يساوي 2، فيُمكِننا أن نضيف عقدتين أُخرَيَيْنِ، وبالتالي، يصبح عدد العقد <code>n</code> في الشجرة مساويًا للقيمة 3.
	</li>
	<li>
		وإذا كان ارتفاع الشجرة <code>h</code> يساوي 3، فيُمكِننا أن نضيف ما يصل إلى أربعِ عقدٍ أخرى، وبالتالي، يصبح عدد العقد <code>n</code> مساويًا للقيمة 7.
	</li>
	<li>
		وإذا كان ارتفاع الشجرة <code>h</code> يساوي 4، يُمكِننا أن نضيف ما يصل إلى ثماني عقدٍ أخرى، وبالتالي، يصبح عدد العقد <code>n</code> مساويًا للقيمة 15.
	</li>
</ul>

<p>
	ربما لاحظت النمط المشترك بين تلك الأمثلة. إذا رقَّمنا مستويات الشجرة تدريجيًّا من 1 إلى h، فإن عدد العقد في أيّ مستوىً <code>i</code> يَصِل إلى 2<sup>i-1</sup> كحدٍّ أقصى، وبالتالي، يكون عددُ العقدِ الإجماليُّ في عدد <code>h</code> من المستويات هو 2<sup>h</sup>-1. إذا كان:
</p>

<p style="text-align: center;">
	n = 2<sup>h</sup> - 1
</p>

<p>
	بتطبيق لوغاريتم الأساس 2 على طرفي المعادلة السابقة، نحصل على التالي:
</p>

<p style="text-align: center;">
	log<sub>2</sub> n ≈ h
</p>

<p>
	إذًا، يتناسب ارتفاع الشجرة مع log(n)‎ إذا كانت الشجرة ممتلئةً؛ أي إذا كان كل مستوىً فيها يحتوي على العدد الأقصى المسموح به من العقد.
</p>

<p>
	وبالتالي، يتناسب زمنُ البحثِ عن مفتاحٍ ضمن شجرةِ بحثٍ ثنائيّةٍ مع log(n)‎. يُعدّ ذلك صحيحًا سواءٌ أكانت الشجرةُ ممتلئة كلّيًّا أم جزئيًا، ولكنه ليس صحيحًا في المطلق، وهو ما سنراه لاحقًا.
</p>

<p>
	يُطلَق على الخوارزميات التي تَستغرِق زمنًا يتناسب مع log(n)‎ اسم "خوارزمية لوغاريتمية"، وتنتمي إلى ترتيب النمو O(log(n))‎.
</p>

<h2>
	تمرين 10
</h2>

<p>
	ستكتب في هذا التمرين تنفيذًا للواجهة <code>Map</code> باستخدام شجرةِ بحثٍ ثنائيّةٍ.
</p>

<p>
	انظر إلى التعريفِ المبدئيِّ للصنف <code>MyTreeMap</code>:
</p>

<pre class="ipsCode">public class MyTreeMap&lt;K, V&gt; implements Map&lt;K, V&gt; {

    private int size = 0;
    private Node root = null;
</pre>

<p>
	يحتفظ متغيّرُ النسخةِ <code>size</code> بعدد المفاتيح بينما يحتوي <code>root</code> على مرجع reference يشير إلى عقدة الجذر الخاصّةِ بالشجرة. إذا كانت الشجرة فارغةً، يحتوي <code>root</code> على القيمة <code>null</code> وتكون قيمة <code>size</code> مساويةً للصفر.
</p>

<p>
	انظر إلى الصنف <code>Node</code> المُعرَّف داخل الصنف <code>MyTreeMap</code>:
</p>

<pre class="ipsCode">    protected class Node {
        public K key;
        public V value;
        public Node left = null;
        public Node right = null;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
</pre>

<p>
	تحتوي كل عقدةٍ على زوج مفتاح/قيمة وعلى مراجعَ تشير إلى العقد الأبناء <code>left</code> و <code>right</code>. قد تكون إحداهما أو كلتاهما فارغة أي تحتوي على القيمة <code>null</code>.
</p>

<p>
	من السهل تنفيذ بعض توابع الواجهة <code>Map</code> مثل <code>size</code> و <code>clear</code>:
</p>

<pre class="ipsCode">    public int size() {
        return size;
    }

    public void clear() {
        size = 0;
        root = null;
    }
</pre>

<p>
	من الواضح أن التابع <code>size</code> يَستغرِق زمنًا ثابتًا.
</p>

<p>
	قد تظن للوهلة الأولى أن التابع <code>clear</code> يستغرق زمنًا ثابتًا، ولكن فكر بالتالي: عندما تُضبَط قيمة <code>root</code> إلى القيمة <code>null</code>، يستعيد كانسُ المهملات garbage collector العقدَ الموجودة في الشجرة ويَستغرِق لإنجاز ذلك زمنًا خطيًا. هل ينبغي أن يُحسَب العمل الذي يقوم به كانس المهملات؟ ربما.
</p>

<p>
	ستكتب في القسم التالي تنفيذًا لبعض التوابع الأخرى لا سيّما التابعين الأهمّ <code>get</code> و <code>put</code>.
</p>

<h2>
	تنفيذ الصنف TreeMap
</h2>

<p>
	ستجد ملفات الشيفرة التالية في <a href="https://github.com/AllenDowney/ThinkDataStructures" rel="external nofollow">مستودع الكتاب</a>:
</p>

<ul>
	<li>
		<code>MyTreeMap.java</code>: يحتوي على الشيفرة المُوضَّحة في الأعلى مع تصورٍ مبدئيٍّ للتوابع غير المكتملة.
	</li>
	<li>
		<code>MyTreeMapTest.java</code> : يحتوي على اختبارات وحدةٍ للصنف <code>MyTreeMap</code>.
	</li>
</ul>

<p>
	نفِّذ الأمر <code>ant build</code> لتصريف ملفات الشيفرة، ثم نفِّذ الأمر <code>ant MyTreeMapTest</code>. قد تفشل بعض الاختبارات لأنّ هناك بعض التوابع التي ينبغي عليك إكمالها أولًا.
</p>

<p>
	وفَّرنا تصورًا مبدئيًا للتابعين <code>get</code> و <code>containsKey</code>. يَستخدِم كلاهما التابع <code>findNode</code> المُعرَّف باستخدام المُعدِّل <code>private</code>، لأنه ليس جزءًا من الواجهة <code>Map</code>. انظر إلى بداية تعريفه:
</p>

<pre class="ipsCode">    private Node findNode(Object target) {
        if (target == null) {
            throw new IllegalArgumentException();
        }

        @SuppressWarnings("unchecked")
        Comparable&lt;? super K&gt; k = (Comparable&lt;? super K&gt;) target;

        // TODO: FILL THIS IN!
        return null;
    }
</pre>

<p>
	يشير المعامل <code>target</code> إلى المفتاح الذي نبحث عنه. إذا كانت قيمة <code>target</code> تساوي <code>null</code>، يُبلِّغ التابع <code>findNode</code> عن اعتراضٍ exception. في الواقع، بإمكان بعض تنفيذات الواجهة <code>Map</code> معالجة الحالات التي تكون فيها قيمة المفتاح فارغة، ولكن لأننا في هذا التنفيذ نَستخدِم شجرة بحثٍ ثنائيّةٍ، فلا بُدّ أن نتمكّن من موازنةِ المفاتيح، ولذلك، يُشكِّل التعامل مع القيمة <code>null</code> مشكلةً، ولذا ولكي نُبسِّط الأمور، لن نَسمَح لهذا التنفيذ باستخدام القيمة <code>null</code> كمفتاح.
</p>

<p>
	تُوضِّح الأسطر التالية كيف يمكِننا أن نوازن قيمة المفتاح <code>target</code> مع قيمة مفتاحٍ ضمن الشجرة. تشير نسخةُ التابعين <code>get</code> و <code>containsKey</code> إلى أن المُصرِّف يتعامل مع <code>target</code> كما لو أنه ينتمي إلى النوع <code>Object</code>، ولأننا نريد موازنته مع المفاتيح، فإننا نحوِّل نوع <code>target</code> إلى النوع <code>Comparable&lt;? super K&gt;‎</code> لكي يُصبِح قابلًا للموازنة مع كائنٍ من النوع <code>K</code> أو أيٍّ من أصنافه الأعلى superclass. يُمكِنك قراءة المزيد عن <a href="http://thinkdast.com/gentut" rel="external nofollow">أنواع محارف البدل (باللغة الإنجليزية)</a>.
</p>

<p>
	ليس المقصودُ من هذا التمرين احترافَ التعامل مع نظام الأنواع في لغة جافا، فدورك فقط هو أن تُكمِل التابع <code>findNode</code>. إذا وجد ذلك التابع عقدةً تحتوي على قيمة <code>target</code> كمفتاح، فعليه أن يعيدها، أما إذا لم يجدها، فعليه أن يعيد القيمة <code>null</code>. ينبغي أن تنجح اختبارات التابعين <code>get</code> و <code>containsKey</code> بعد أن تنتهي من إكمال هذا التابع.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<strong>ملحوظة</strong>: ينبغي للحل الخاص بك أن يبحث في مسارٍ واحدٍ فقط ضمن الشجرة لا أن يبحث في كامل الشجرة، أي سيَستغرِق زمنًا يتناسب مع ارتفاع الشجرة.
		</p>
	</div>
</blockquote>

<p>
	والآن، عليك أن تُكمِل التابع <code>containsValue</code>، ولمساعدتك على ذلك، وفَّرنا التابع المساعد <code>equals</code> الذي يُوازِن بين قيمة <code>target</code> وقيمة مفتاحٍ معيّنٍ. على العكس من المفاتيح، قد لا تكون القيم المُخزَّنة في الشجرة قابلةً للموازنة، وبالتالي، لا يُمكِننا أن نَستخدِم معها التابع <code>compareTo</code>، وإنما علينا أن نَستدعِيَ التابعَ <code>equals</code> بالمتغير <code>target</code>.
</p>

<p>
	بخلاف التابع <code>findNode</code>، سيضطرّ التابع <code>containsValue</code> للبحث في كامل الشجرة، أي يتناسب زمن تشغيله مع عدد المفاتيح n وليس مع ارتفاع الشجرة <code>h</code>.
</p>

<p>
	والآن، أكمل متنَ التابع <code>put</code>. وفَّرنا له شيفرةً مبدئيةً تعالج الحالات البسيطة فقط:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9831_11" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> V put</span><span class="pun">(</span><span class="pln">K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IllegalArgumentException</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">root </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            root </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
            size</span><span class="pun">++;</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> null</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> putHelper</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> V putHelper</span><span class="pun">(</span><span class="typ">Node</span><span class="pln"> node</span><span class="pun">,</span><span class="pln"> K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// TODO: Fill this in.</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	إذا حاولت استخدَام القيمة الفارغة <code>null</code> كمفتاحٍ، سيُبلّغ <code>put</code> عن اعتراض؛ وإذا كانت الشجرة فارغةً، سيُنشِئ التابع <code>put</code> عقدةً جديدةً، ويُهيِّئُ المتغير <code>root</code> المُعرَّف فيها؛ أما إذا لم تكن فارغةً، فإنه يَستدعِي التابع <code>putHelper</code> المُعرَّف باستخدام المُعدِّل <code>private</code> لأنه ليس جزءًا من الواجهة <code>Map</code>.
</p>

<p>
	أكمل متنَ التابع <code>putHelper</code> واجعله يبحث ضمن الشجرة وفقًا لما يلي:
</p>

<ol>
	<li>
		إذا كان المفتاح <code>key</code> موجودًا بالفعل ضمن الشجرة، عليه أن يَستبدِل القيمة الجديدة بالقيمة القديمة، ثم يعيدها.
	</li>
	<li>
		إذا لم يكن المفتاح <code>key</code> موجودًا في الشجرة، فعليه أن يُنشِىء عقدةً جديدةً، ثم يضيفها إلى المكان الصحيح، وأخيرًا، يعيد القيمة <code>null</code>.
	</li>
</ol>

<p>
	ينبغي أن يَستغرِق التابع <code>put</code> زمنًا يتناسب مع ارتفاع الشجرة h وليس مع عدد العناصر n. سيكون من الأفضل لو بحثت في الشجرة مرةً واحدةً فقط، ولكن إذا كان البحث فيها مرّتين أسهلَ بالنسبة لك، فلا بأس. سيكون التنفيذ أبطأ، ولكنّه لن يؤثر على ترتيب نموه.
</p>

<p>
	وأخيرًا، عليك أن تُكمِل متن التابع <code>keySet</code>. يعيد ذلك التابع -وفقًا لـ<a href="http://thinkdast.com/mapkeyset" rel="external nofollow">للتوثيق (باللغة الإنجليزية)</a>- قيمة من النوع <code>Set</code> بإمكانها المرور عبر جميع مفاتيح الشجرة بترتيبٍ تصاعديٍّ وفقًا للتابع <code>compareTo</code>. كنا قد اِستخدَمنا الصنف <code>HashSet</code> في مقالة <a href="https://academy.hsoub.com/programming/advanced/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%B1%D9%8A%D8%B7%D8%A9-%D9%88%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%81%D9%87%D8%B1%D8%B3-indexer-r1384/" rel="">استخدام خريطة ومجموعة لبناء مُفهرِس Indexer</a>. يُعدّ ذلك الصنف تنفيذًا للواجهة <code>Set</code> ولكنه لا يحافظ على ترتيب المفاتيح. في المقابل، يتوفَّر <a href="http://thinkdast.com/linkedhashset" rel="external nofollow">التنفيذ <code>LinkedHashSet</code></a> الذي يحافظ على ترتيب المفاتيح.
</p>

<p>
	يُنشِئ التابع <code>keySet</code> قيمةً من النوع <code>LinkedHashSet</code> ويعيدها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9831_13" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Set</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;</span><span class="pln"> keySet</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Set</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">set</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LinkedHashSet</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">&gt;();</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">set</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	عليك أن تُكمِل هذا التابع بحيث تجعلُه يضيفُ المفاتيح من الشجرة إلى المجموعة <code>set</code> بترتيبٍ تصاعديّ.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			 
		</p>

		<p>
			<strong>تلميح</strong>: قد تحتاج إلى كتابةٍ تابعٍ مساعدٍ، وقد ترغب بجعله تعاوديًّا recursive.
		</p>

		<p>
			 
		</p>
	</div>
</blockquote>

<p>
	ينبغي أن تنجح جميع الاختبارات بعد أن تنتهي من إكمال هذا التابع. سنَعرِض حل هذا التمرين ونفحص أداء التوابع الأساسية في الصنف في مقالٍ آخر من هذه السلسلة.
</p>

<p>
	ترجمة -بتصرّف- للفصل <a href="https://greenteapress.com/thinkdast/html/thinkdast013.html" rel="external nofollow">Chapter 12: TreeMap</a> من كتاب <a href="https://greenteapress.com/thinkdast/html/index.html" rel="external nofollow">Think Data Structures: Algorithms and Information Retrieval in Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/java/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B4%D8%AC%D8%A7%D8%B1-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D9%84%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A3%D8%B4%D8%AC%D8%A7%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D9%86%D8%A9-balanced-trees-%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-r1454/" rel="">استخدام أشجار البحث الثنائية والأشجار المتزنة balanced trees لتنفيذ الخرائط</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%A3%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashmap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1452/" rel="">تحسين أداء الخرائط المنفذة باستخدام التعمية HashMap في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashing-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1416/" rel="">تنفيذ الخرائط باستخدام التعمية hashing في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1389/" rel="">تحليل زمن تشغيل الخرائط المنفذة باستخدام مصفوفة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-maps-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1430/" rel="">الخرائط Maps في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1453</guid><pubDate>Sat, 12 Feb 2022 16:02:00 +0000</pubDate></item><item><title>&#x645;&#x639;&#x627;&#x644;&#x62C;&#x629; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1460/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61f83036dcd9e_-.png.86d00a889b8f96d1008c8c230569aa36.png" /></p>

<p>
	سنفحص خلال هذا القسم بعض الأمثلة البرمجية على تعامل البرامج مع الملفات باستخدام التقنيات المُوضحَّة التي عرضناها في المقالين السابقين، مقال <a href="https://academy.hsoub.com/programming/java/%D9%82%D9%86%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D8%AE%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%B1%D8%AC-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%AA%D9%8A-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%88%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1451/" rel="">قنوات الدخل والخرج وعمليتي القراءة والكتابة في جافا</a> ومقال <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/" rel="">مدخل إلى التعامل مع الملفات في جافا</a>.
</p>

<h2>
	نسخ الملفات
</h2>

<p>
	سنناقش الآن برنامج سطر أوامرٍ بسيط لنسخ الملفات، حيث تُعدّ عملية نسخ الملفات واحدةً من أكثر العمليات شيوعًا، ولهذا تُوفِّر جميع <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">أنظمة التشغيل</a> أمرًا مُخصَّصًا لتلك العملية بالفعل، ولكن ما يزال من المفيد من الناحية التعليمية أن ننظر إلى طريقة تنفيذ ذلك باستخدام برنامج جافا. تتشابه غالبية العمليات على الملفات مع عملية نسخ ملف، باستثناء اختلاف طريقة معالجتها لبيانات الملف المُدْخَلة قبل إعادة كتابتها إلى ملف الخرج؛ أي يُمكِن كتابة برامج لكل تلك العمليات بنفس الكيفية تقريبًا. تعرَّضنا في فصل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%85%D9%84%D8%A7%D8%AA-parameters-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1088/" rel="">المعاملات (parameters) في جافا</a> لبرنامجٍ يَستخدِم الصنف <code>TextIO</code> لنسخ ملفاتٍ نصية، بينما يَعمَل البرنامج بالأسفل مع جميع أنواع الملفات.
</p>

<p>
	ينبغي أن يتمكَّن البرنامج من نسخ أي ملف، وبالتالي لا يُمكِن للملف أن يكون بصيغةٍ مقروءة، وهذا يَعنِي أننا سنضطّر لمعالجته باستخدام أصناف مجاري البايتات <code>InputStream</code> و <code>OutputStream</code>. يَنسَخ البرنامج البيانات من مجرًى من النوع <code>InputStream</code> إلى مجرًى آخر من النوع <code>OutputStream</code>، بحيث يَنسَخ بايتًا واحدًا في كل مرة.
</p>

<p>
	إذا كان <code>source</code> متغيرًا يُشير إلى مجرى الدخل من الصنف <code>InputStream</code>، فستقرأ الدالة <code>source.read()‎</code> بايتًا واحدًا. تعيد تلك الدالة القيمة "-1" بعد الانتهاء من قراءة كلِّ البايتات الموجودة بملف الدْخَل. بالمثل، إذا كان <code>copy</code> مُتغيّرًا يُشير إلى مجرى الخرج من الصنف <code>OutputStream</code>، فستكتب الدالة <code>copy.write(b)‎</code> بايتًا واحدًا في ملف الخرج. يُمكِننا بناءً على ما سبق كتابة البرنامج بهيئة حلقة <code>while</code> محاطةً بتعليمة <code>try..catch</code>؛ نظرًا لإمكانية عمليات الدْخَل والخرج في التبليغ عن اعتراضات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_12" style="">
<span class="kwd">while</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">int</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> source</span><span class="pun">.</span><span class="pln">read</span><span class="pun">();</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
   copy</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَستقبِل أمر نسخ الملفات بنظام تشغيل، مثل UNIX وسطاء <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a> command line arguments لتخصيص أسماء الملفات المطلوبة، حيث يستطيع المُستخدِم مثلًا كتابة أمرٍ، مثل <code>copy original.dat backup.dat</code>، لينسخ ملفًا موجودًا اسمه "original.dat" إلى ملفٍ اسمه "backup.dat".
</p>

<p>
	تستطيع برامج جافا استخدام وسطاء سطر الأوامر بنفس الطريقة؛ حيث تُخزَّن قيمها ضمن مصفوفةٍ من السلاسل النصية اسمها <code>args</code>، والتي يَستقبِلها البرنامج <code>main()‎</code> مثل معاملٍ، ويستطيع بذلك البرنامج استرجاع القيم المُمرَّرة للوسطاء (انظر فصل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%85%D9%84%D8%A7%D8%AA-parameters-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1088/" rel="">المعاملات (parameters) في جافا</a>). على سبيل المثال، إذا كان "CopyFile" هو اسم البرنامج، وشَغّله المُستخدِم بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_16" style="">
<span class="pln">java </span><span class="typ">CopyFile</span><span class="pln"> work</span><span class="pun">.</span><span class="pln">dat oldwork</span><span class="pun">.</span><span class="pln">dat</span></pre>

<p>
	فستُساوِي قيمة <code>args[0]‎</code> بالبرنامج السلسلة النصية "work.dat"؛ أما قيمة <code>args[1]‎</code> فستُساوِي <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">السلسلة النصية</a> "oldwork.dat". تُشير قيمة <code>args.length</code> إلى عدد الوسطاء المُمرَّرين.
</p>

<p>
	يَحصُل برنامج <a href="http://math.hws.edu/javanotes/source/chapter11/CopyFile.java" rel="external nofollow">CopyFile.java</a> على اسمي الملفين من خلال وسطاء سطر الأوامر، ويَطبَع رسالة خطأ إن لم يَجِدهما. هناك طريقتان لاستخدام البرنامج، هما:
</p>

<ul>
<li>
		أولًا، قد يحتوي سطر الأوامر ببساطةٍ على اسمي ملفين، ويَطبَع البرنامج في تلك الحالة رسالة خطأ وينتهي إذا كان ملف الخرج المُخصَّص موجودًا مُسبقًا؛ لكي لا يَكْتُب بملفٍ مهمٍ عن طريق الخطأ.
	</li>
	<li>
		ثانيًا، قد يحتوى سطر الأوامر على ثلاثة وسطاء، ولا بُدّ في تلك الحالة أن يكون الوسيط الأول هو الخيار "‎-f"؛ أما الثاني والثالث فهما اسما الملفين. تُعدّل كتابة الوسيط "‎-f" من سلوك البرنامج، حيث يُفسِّره البرنامج على أنه رُخصةً للكتابة بملف الخرج حتى لو كان موجودًا مُسبقًا. لاحِظ أن "‎-f" هي في الواقع اختصار لكلمة "force"؛ نظرًا لأنها تجبر البرنامج على نسخ الملف حتى في الحالات التي كان البرنامج سيتعامل معها كما لو كانت خطأً بصورةٍ افتراضية. يُمكِنك الاطلاع على شيفرة البرنامج لترى طريقة تفسيره لوسطاء سطر الأوامر:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_19" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CopyFile</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

      </span><span class="typ">String</span><span class="pln"> sourceName</span><span class="pun">;</span><span class="pln">   </span><span class="com">// اسم ملف المصدر كما خُصّص بسطر الأوامر</span><span class="pln">
      </span><span class="typ">String</span><span class="pln"> copyName</span><span class="pun">;</span><span class="pln">     </span><span class="com">// اسم ملف النسخة المُخصَّص</span><span class="pln">

      </span><span class="typ">InputStream</span><span class="pln"> source</span><span class="pun">;</span><span class="pln">  </span><span class="com">// مجرًى للقراءة من ملف المصدر</span><span class="pln">
      </span><span class="typ">OutputStream</span><span class="pln"> copy</span><span class="pun">;</span><span class="pln">   </span><span class="com">// مجرًى للكتابة بنسخة الملف</span><span class="pln">
       </span><span class="com">// ‫اضبطها إلى القيمة true إذا كان الخيار "f-" موجودًا بسطر الأوامر</span><span class="pln">
      boolean force</span><span class="pun">;</span><span class="pln">  
      </span><span class="typ">int</span><span class="pln"> byteCount</span><span class="pun">;</span><span class="pln">  </span><span class="com">// عدد البايتات المنسوخة حتى الآن</span><span class="pln">

      </span><span class="com">// 2</span><span class="pln">

      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">args</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"-f"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         sourceName </span><span class="pun">=</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">1</span><span class="pun">];</span><span class="pln">
         copyName </span><span class="pun">=</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">2</span><span class="pun">];</span><span class="pln">
         force </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">args</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         sourceName </span><span class="pun">=</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
         copyName </span><span class="pun">=</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">1</span><span class="pun">];</span><span class="pln">
         force </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">
                 </span><span class="str">"Usage:  java CopyFile &lt;source-file&gt; &lt;copy-name&gt;"</span><span class="pun">);</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">
                 </span><span class="str">"    or  java CopyFile -f &lt;source-file&gt; &lt;copy-name&gt;"</span><span class="pun">);</span><span class="pln">
         </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="com">/* أنشئ مجرى الدخل، وأنهِ البرنامج في حالة حدوث خطأ */</span><span class="pln">

      </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         source </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileInputStream</span><span class="pun">(</span><span class="pln">sourceName</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't find file \""</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> sourceName </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\"."</span><span class="pun">);</span><span class="pln">
         </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="com">// 4</span><span class="pln">

      </span><span class="typ">File</span><span class="pln"> file </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln">copyName</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">file</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> force </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">
               </span><span class="str">"Output file exists.  Use the -f option to replace it."</span><span class="pun">);</span><span class="pln">
          </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  
      </span><span class="pun">}</span><span class="pln">

      </span><span class="com">/* أنشئ مجرى الخرج وأنهِ البرنامج في حالة حدوث خطأ */</span><span class="pln">

      </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         copy </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileOutputStream</span><span class="pun">(</span><span class="pln">copyName</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't open output file \""</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> copyName </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\"."</span><span class="pun">);</span><span class="pln">
         </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="com">// 3</span><span class="pln">

      byteCount </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

      </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">int</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> source</span><span class="pun">.</span><span class="pln">read</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
               </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
            copy</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
            byteCount</span><span class="pun">++;</span><span class="pln">
         </span><span class="pun">}</span><span class="pln">
         source</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
         copy</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Successfully copied "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> byteCount </span><span class="pun">+</span><span class="pln"> </span><span class="str">" bytes."</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error occurred while copying.  "</span><span class="pln">
                                   </span><span class="pun">+</span><span class="pln"> byteCount </span><span class="pun">+</span><span class="pln"> </span><span class="str">" bytes copied."</span><span class="pun">);</span><span class="pln">
         </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

   </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end main()</span><span class="pln">


</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end class CopyFile</span></pre>

<p>
	حيث يُقصد بـ:
</p>

<ul>
<li>
		[1]: أنشئ نسخةً من ملف. يجب تخصيص كُلٍ من اسم الملف الأصلي، واسم ملف النسخة على أنهما وسائطٌ بسطر الأوامر. يُمكِننا بالإضافة إلى ذلك كتابة الخيار "‎-f" على أنه وسيطٌ أول، وسيكتب البرنامج في تلك الحالة على الملف الذي يحمل اسم ملف النسخة في حالة وجوده مسبقًا؛ أما إذا لم يكن هذا الخيار موجودًا، فسيبلِّغ البرنامج عن خطأ وينتهي إذا كان الملف موجودًا. يُبلِّغ البرنامج أيضًا عن عدد البايتات التي نسخها من الملف.
	</li>
	<li>
		[2]: احصل على أسماء الملفات من سطر الأوامر وافحص فيما إذا كان الخيار "‎-f" موجودًا. إذا لم يكن الأمر بأيٍّ من الصيغ المحتملة، اطبع رسالة خطأ وأنهِ البرنامج.
	</li>
	<li>
		[3]: اِنسَخ بايتًا واحدًا بكل مرة من مجرى الدخل إلى مجرى الخرج حتى يعيد التابع <code>read()‎</code> القيمة "-1"، والتي تُعدّ إشارةً إلى الوصول إلى نهاية المجرى. إذا حدث خطأٌ، اطبع رسالة خطأ، وكذلك اطبع رسالةً في حالة نسخ الملف بنجاح.
	</li>
	<li>
		[4]: إذا كان ملف الخرج موجودًا بالفعل، ولم يُخصِّص المُستخدِم الخيار "‎-f"، اطبع رسالة خطأ وأنهِ البرنامج.
	</li>
</ul>
<p>
	لا تَعمَل عملية نسخ بايتٍ واحدٍ بكل مرة بالكفاءة المطلوبة، حيث يُمكِن تحسينها باستخدام نسخٍ أخرى من التابعين <code>read()‎</code> و <code>write()‎</code>، والتي بإمكانها قراءة وكتابة عدة بايتات بنفس الوقت (انظر واجهة برمجة التطبيقات لمزيدٍ من التفاصيل). يُمكننا بدلًا من ذلك أن نحيط مجاري تدفق الدخل والخرج بكائناتٍ من النوع <code>BufferedInputStream</code> و <code>BufferedOutputStream</code>، والتي يُمكِنها قراءة أو كتابة كتلٍ من البيانات من وإلى الملف مباشرةً، ويتطلّب ذلك تعديل سطرين فقط من البرنامج المسؤول عن إنشاء مجاري التدفق. فمثلًا، يُمكِننا أن نُنشِئ مجرى الدخل على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_21" style="">
<span class="pln">source </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedInputStream</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileInputStream</span><span class="pun">(</span><span class="pln">sourceName</span><span class="pun">));</span></pre>

<p>
	وبذلك يُمكِننا استخدام المجرى المُدعَّم بخاصية التخزين المؤقت buffered stream بنفس طريقة استخدام المجرى العادي.
</p>

<p>
	يُمكِنك الإطلاع على البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter11/CopyFileAsResources.java" rel="external nofollow">CopyFileAsResources.java</a>، والذي يُنجز نفس مهمة البرنامج <code>CopyFile</code>، ولكنه يَستخدِم نمط المورد resource pattern ضمن تعليمة <code>try..catch</code>؛ ليتأكَّد من غلق المجاري بجميع الحالات، وهو ما ناقشناه بنهاية القسم "تعليمة Try" من فصل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%A7%D8%AA-exceptions-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1309/" rel="">الاستثناءات exceptions وتعليمة try..catch في جافا</a>.
</p>

<h2>
	البيانات الدائمة
</h2>

<p>
	بمجرد انتهاء برنامجٍ معينٍ من العمل، تُلغَى جميع البيانات التي خزَّنها البرنامج بمتغيراتٍ أو كائناتٍ أثناء تنفيذه، مع أننا قد نرغب أحيانًا في الإبقاء على بعض من تلك البيانات بحيث تظل متاحةً للبرنامج عند تنفيذه مرةً أخرى. يطرح ذلك السؤال التالي: كيف يُمكِننا الاحتفاظ بالبيانات وإتاحتها للبرنامج مرةً أخرى؟ الإجابة ببساطة هي بتخزينها بملف، أو <a href="https://academy.hsoub.com/devops/servers/databases/%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-%D9%81%D9%8A-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%AA%D8%B5%D9%85%D9%8A%D9%85%D9%87%D8%A7-r519/" rel="">قاعدة بيانات database</a> لبعض التطبيقات، رغم أننا إذا شئنا الدقة فهي تُعدّ ملفات أيضًا؛ حيث تكون البيانات الموجودة ضمن قاعدة بيانات مُخزَّنةً بالنهاية ضمن ملفات.
</p>

<p>
	لنأخذ مثالًا على ذلك، وهو برنامج "دليل هاتف" يَسمَح للمُستخدِم بالاحتفاظ بقائمةٍ من الأسماء وأرقام الهواتف. لن يكون للبرنامج أي معنًى إذا اضطّر المُستخدِم لإعادة إنشاء القائمة من الصفر بكل مرةٍ يُشغِّل فيها البرنامج، وإنما ينبغي أن نُفكِر بدليل الهاتف كما لو أنه <strong>تجميعةٌ دائمة persistent</strong> من البيانات، وأن نفكر بالبرنامج على أنه مجرد واجهةٍ لتلك التجميعة. سيَسمَح البرنامج للمُستخدِم بالبحث بدليل الهاتف من خلال الاسم، وكذلك بإدخال بياناتٍ جديدة. وينبغي بالطبع الاحتفاظ بأي تغييراتٍ يُجريها المُستخدِم لما بعد انتهاء البرنامج.
</p>

<p>
	يُعد البرنامج <a href="http://math.hws.edu/javanotes/source/chapter11/PhoneDirectoryFileDemo.java" rel="external nofollow">PhoneDirectoryFileDemo.java</a> تنفيذًا implementation بسيطًا لتلك الفكرة. لاحِظ أنه صُمِّم ليكون فقط مثالًا على طريقة توظيف الملفات ضمن برنامج، فلا تُحملّه أكثر من حجمه، فهو ليس برنامجًا حقيقيًا. يُخزِّن البرنامج بيانات دليل الهاتف بملفٍ اسمه "‎.phone<em>book</em>demo" بالمجلد الرئيسي للمُستخدِم، والذي يُحدِّده البرنامج بالاستعانة بالتابع <code>System.getProperty()‎</code> الذي ذكرناه في مقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">مدخل إلى التعامل مع الملفات في جافا</a> المشار إليه في الأعلى.
</p>

<p>
	عندما يبدأ البرنامج بالعمل، فإنه يَفحَص أولًا فيما إذا كان الملف موجودًا بالفعل؛ فإذا كان موجودًا، فإنه يحتوي بالضرورة على بيانات دليل الهاتف الخاصة بالمُستخدِم، والتي خُزِّنت أثناء تشغيله لنفس البرنامج بمرةٍ سابقة، ويقرأ البرنامج في تلك الحالة بيانات الملف، ويُخزِّنها بكائنٍ اسمه <code>phoneBook</code> من النوع <code>TreeMap</code>؛ حيث يُمثِّل هذا الكائن دليل الهاتف أثناء تشغيل البرنامج (انظر القسم "واجهة تمثيل الخرائط" من فصل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-maps-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1430/" rel="">الخرائط Maps في جافا</a>).
</p>

<p>
	علينا الآن الاتفاق على طريقة تمثيل بيانات دليل الهاتف قبل تخزينها بملف. سنختار تمثيلًا بسيطًا، يُمثِّل فيه كل سطرٍ ضمن الملف مُدْخَلًا واحدًا مكوَّنًا من اسم ورقم هاتف يَفصِل بينهما علامة النسبة المئوية %. تقرأ الشيفرة التالية ملف بيانات دليل الهاتف إذا كان موجودًا ومكتوبًا وفقًا لطريقة التمثيل المُتفَق عليها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_24" style="">
<span class="typ">File</span><span class="pln"> userHomeDirectory </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">getProperty</span><span class="pun">(</span><span class="str">"user.home"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="typ">File</span><span class="pln"> dataFile </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln"> userHomeDirectory</span><span class="pun">,</span><span class="pln"> </span><span class="str">".phone_book_data"</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
        </span><span class="com">// A file named .phone_book_data in the user's home directory.</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> dataFile</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"No phone book data file found.  A new one"</span><span class="pun">);</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"will be created, if you add any entries."</span><span class="pun">);</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"File name:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> dataFile</span><span class="pun">.</span><span class="pln">getAbsolutePath</span><span class="pun">());</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Reading phone book data..."</span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">try</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pln"> scanner </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="pln">dataFile</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">scanner</span><span class="pun">.</span><span class="pln">hasNextLine</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
             </span><span class="com">// اقرأ سطرًا واحدًا من الملف يحتوي على زوج اسم ورقم هاتف</span><span class="pln">

         </span><span class="typ">String</span><span class="pln"> phoneEntry </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextLine</span><span class="pun">();</span><span class="pln">
         </span><span class="typ">int</span><span class="pln"> separatorPosition </span><span class="pun">=</span><span class="pln"> phoneEntry</span><span class="pun">.</span><span class="pln">indexOf</span><span class="pun">(</span><span class="str">'%'</span><span class="pun">);</span><span class="pln">
         </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">separatorPosition </span><span class="pun">==</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"File is not a phonebook data file."</span><span class="pun">);</span><span class="pln">
         name </span><span class="pun">=</span><span class="pln"> phoneEntry</span><span class="pun">.</span><span class="pln">substring</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> separatorPosition</span><span class="pun">);</span><span class="pln">
         number </span><span class="pun">=</span><span class="pln"> phoneEntry</span><span class="pun">.</span><span class="pln">substring</span><span class="pun">(</span><span class="pln">separatorPosition</span><span class="pun">+</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
         phoneBook</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln">number</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error in phone book data file."</span><span class="pun">);</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"File name:  "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> dataFile</span><span class="pun">.</span><span class="pln">getAbsolutePath</span><span class="pun">());</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"This program cannot continue."</span><span class="pun">);</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد ذلك، يَسمَح البرنامج للمُستخدِم بإجراء عدّة عملياتٍ على دليل الهاتف، بما في ذلك تعديل محتوياته؛ فإذا عدَّل المُستخدِم أيًا من بيانات دليل الهاتف بينما البرنامج مُشغَّل، فسيُجرى هذا التعديل فقط على كائن الصنف <code>TreeMap</code>. وعندما يحين موعد انتهاء البرنامج، يُمكِننا عندها كتابة تلك البيانات المُعدَّلة بالملف باستخدام الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_26" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">changed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Saving phone directory changes to file "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> 
         dataFile</span><span class="pun">.</span><span class="pln">getAbsolutePath</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" ..."</span><span class="pun">);</span><span class="pln">
   </span><span class="typ">PrintWriter</span><span class="pln"> out</span><span class="pun">;</span><span class="pln">
   </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      out </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileWriter</span><span class="pun">(</span><span class="pln">dataFile</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"ERROR: Can't open data file for output."</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">Map</span><span class="pun">.</span><span class="typ">Entry</span><span class="pun">&lt;</span><span class="typ">String</span><span class="pun">,</span><span class="typ">String</span><span class="pun">&gt;</span><span class="pln"> entry </span><span class="pun">:</span><span class="pln"> phoneBook</span><span class="pun">.</span><span class="pln">entrySet</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln">
      out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">entry</span><span class="pun">.</span><span class="pln">getKey</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"%"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> entry</span><span class="pun">.</span><span class="pln">getValue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">out</span><span class="pun">.</span><span class="pln">checkError</span><span class="pun">())</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"ERROR: Some error occurred while writing data file."</span><span class="pun">);</span><span class="pln">
   </span><span class="kwd">else</span><span class="pln">
      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Done."</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ينتج عن ذلك أن جميع البيانات -بما في ذلك التعديلات التي أجراها المُستخدِم- ستكون متاحةً بالمرة التالية التي يُنفَّذ خلالها البرنامج. عرضنا بالأعلى شيفرة البرنامج المُتعلِّقة بمعالجة الملف فقط، ولكن يُمكِنك بالطبع الإطلاع على باقي أجزاء البرنامج من <a href="http://math.hws.edu/javanotes/source/chapter11/PhoneDirectoryFileDemo.java" rel="external nofollow">هنا</a>.
</p>

<h2>
	حفظ الكائنات بملف
</h2>

<p>
	عندما نرغب بحفظ أي بياناتٍ ضمن ملف، يجب أن نقرّر أولًا صيغة تمثيل تلك البيانات. نظرًا لاتِّباع كُلٍ من برامج الخرج المسؤولة عن كتابة البيانات وبرامج الدْخَل المسؤولة عن قرائتها نفس الصيغة المُقرَّرة، فستُصبح الملفات قابلةً لإعادة الاستخدام. ربما سيكون البرنامج بذلك مكتوبًا كتابةً صحيحة correctness، ولكن لا يُعدّ ذلك الأمر الهام الوحيد، وإنما يجب أيضًا أن تكون طريقة تمثيل البيانات بالملفات متينة (انظر الفصل <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%B5%D8%AD%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D9%88%D9%85%D8%AA%D8%A7%D9%86%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1291/" rel="">مقدمة إلى صحة البرامج ومتانتها في جافا</a>). سنناقش طرائقًا مختلفةً لتمثيل نفس البيانات لفهم ما يعنيه ذلك.
</p>

<p>
	سيعتمد المثال الذي سنُناقشه على المثال <a href="http://math.hws.edu/javanotes/source/chapter7/SimplePaint2.java" rel="external nofollow">SimplePaint2.java</a> من القسم "البرمجة باستخدام ArrayList" من فصل <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D9%8A%D9%86%D8%A7%D9%85%D9%8A%D9%83%D9%8A%D8%A9-arraylists-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1159/" rel="">مفهوم المصفوفات الديناميكية (ArrayLists) في جافا</a> (قد ترغب بتشغّيله لكي تتذكَّر إمكانياته)، حيث يُمكِّن هذا البرنامج المُستخدِم من استخدام الفأرة لرسم بعض الرسوم، وسنُضيف إليه الآن إمكانية القراءة من والكتابة إلى ملف؛ وسيَسمَح ذلك للمُستخدِم بحفظ رسمة معينة بملف، وقراءتها لاحقًا من نفس الملف، مما يُمكِّنه من إكمال العمل عليها لاحقًا. يتطلّب ذلك حفظ جميع البيانات المُتعلّقة بالرسمة ضمن ملف، لكي يتمكَّن البرنامج من إعادة رسمها بالكامل مرةً أخرى بعد قراءته للملف الخاص بالرسمة.
</p>

<p>
	يُمكِنك الإطلاع على النسخة الأحدث من البرنامج بملف الشيفرة المصدرية <a href="http://math.hws.edu/javanotes/source/chapter11/SimplePaintWithFiles.java" rel="external nofollow">SimplePaintWithFiles.java</a>، والتي أضفنا إليها قائمة "File" تُنفِّذ الأمرين "Save" و "Open"؛ لحفظ بيانات البرنامج بملف وكذلك قراءة البيانات المحفوظة بملف مرةً أخرى إلى البرنامج على الترتيب.
</p>

<p>
	تتكوّن بيانات الرسمة من لون الخلفية، وقائمةً بالمنحنيات التي رسمها المُستخدِم. تَتكوَّن بيانات كل منحنًى منها من قائمة نقاطٍ من النوع <code>Point2D</code> المُعرَّف بحزمة <code>javafx.geometry</code>؛ فإذا كان <code>pt</code> متُغيِّرًا من النوع <code>Point2D</code>، فسيُعيد تابعي المُتغيّر <code>pt.getX()‎</code> و <code>pt.getY()‎</code> قيمًا من النوع <code>double</code> تُمثِّل إحداثيات تلك النقطة بالمستوى xy. يُمكِن تخصيص لون كل منحنًى على حدى، كما يُمكِن للمنحنى أن يكون "متماثلًا symmetric"؛ بمعنى أنه بالإضافة إلى رسم المنحنى نفسه، تُرسَم انعكاسات المنحنى الأفقية والرأسية أيضًا. تُخزَّن بيانات كل منحنًى ضمن كائنٍ من النوع <code>CurveData</code> المُعرَّف بالبرنامج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_28" style="">
<span class="com">// 1</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">Color</span><span class="pln"> color</span><span class="pun">;</span><span class="pln">  </span><span class="com">// لون المنحنى</span><span class="pln">
   boolean symmetric</span><span class="pun">;</span><span class="pln">  </span><span class="com">// هل ينبغي رسم الانعكاسات الأفقية والرأسية؟</span><span class="pln">

    </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Point2D</span><span class="pun">&gt;</span><span class="pln"> points</span><span class="pun">;</span><span class="pln">  </span><span class="com">// النقاط الموجودة على المنحنى</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث أن [1] هو كائنٌ من النوع <code>CurveData</code> يُمثِّل البيانات المطلوبة لإعادة رسم إحدى المنحنيات التي رسمها المُستخدِم.
</p>

<p>
	سنَستخدِم قائمةً من النوع <code>ArrayList&lt;CurveData&gt;‎</code> لحمل بيانات جميع المنحنيات التي رَسَمَها المُستخدِم.
</p>

<p>
	لنفكر الآن بالطريقة التي سنَحفَظ بها بيانات الرسمة ضمن ملفٍ نصي. في العموم، علينا تخزين جميع البيانات الضرورية لإعادة رسم الرسمة بملف خرجٍ ووفقًا لصيغةٍ مُحدّدة. بعد ذلك، ينبغي أن يتبِّع التابع المسؤول عن قراءة الملف نفس الصيغة تمامًا أثناء قرائته للبيانات، حيث سيتعيّن عليه اِستخدَام تلك البيانات لإعادة بناء بنى البيانات data structures التي تُمثِّل نفس الرسمة بينما البرنامج مُشغَّل.
</p>

<p>
	سنضطّر عند كتابة البيانات إلى التعبير عنها باستخدام قيم بياناتٍ بسيطة، مثل سلسلةٍ نصية أو قيمةٍ تنتمي لأيٍّ من الأنواع الأساسية primitive types؛ حيث يُمكِننا مثلًا التعبير عن اللون باستخدام ثلاثة أعدادٍ تُمثِّل مكوّنات اللون الأحمر والأخضر والأزرق. قد تكون الفكرة الأولى التي تخطر بذهنك هو مجرد طباعة كل البيانات الضرورية وفقًا لترتيبٍ محدّد، وهي في الواقع ليست الفكرة الأفضل. لنفترض أن <code>out</code> كائنٌ من النوع <code>PrintWriter</code> المُستخدَم لكتابة البيانات بالملف، يُمكِننا إذًا كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_30" style="">
<span class="typ">Color</span><span class="pln"> bgColor </span><span class="pun">=</span><span class="pln"> getBackground</span><span class="pun">();</span><span class="pln">    </span><span class="com">// اكتب لون الخلفية إلى الملف</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> bgColor</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> bgColor</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> bgColor</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">

out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curves</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">       </span><span class="com">// اكتب عدد المنحنيات</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pln"> curve </span><span class="pun">:</span><span class="pln"> curves </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// لكل منحنًى، اكتب ما يلي</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">      </span><span class="com">// لون المنحنى</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">   
   out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">?</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">);</span><span class="pln">   </span><span class="com">// خاصية تماثل المنحنى</span><span class="pln">
   out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">points</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">       </span><span class="com">// عدد النقاط الموجودة على المنحنى</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pln"> pt </span><span class="pun">:</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">points </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">       </span><span class="com">// إحداثيات كل نقطة</span><span class="pln">
      out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getX</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
      out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getY</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيتمكَّن التابع المسؤول عن معالجة الملف من قراءة بياناته، وإعادة إنشاء ما يُكافئها من بنية بيانات. إذا كان التابع يَستخدِم كائنًا من النوع <code>Scanner</code>، وليَكُن اسمه هو <code>scanner</code> لقراءة بيانات الملف، يُمكِننا إذًا كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_32" style="">
<span class="typ">Color</span><span class="pln"> newBackgroundColor</span><span class="pun">;</span><span class="pln">                </span><span class="com">// اقرأ لون الخلفية</span><span class="pln">
</span><span class="kwd">double</span><span class="pln"> red </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">double</span><span class="pln"> green </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">double</span><span class="pln"> blue </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
newBackgroundColor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">red</span><span class="pun">,</span><span class="pln">green</span><span class="pun">,</span><span class="pln">blue</span><span class="pun">);</span><span class="pln">

</span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">CurveData</span><span class="pun">&gt;</span><span class="pln"> newCurves </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> curveCount </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextInt</span><span class="pun">();</span><span class="pln">      </span><span class="com">// عدد المنحنيات المقروءة</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> curveCount</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="typ">CurveData</span><span class="pln"> curve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pun">();</span><span class="pln">
   </span><span class="kwd">double</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">            </span><span class="com">// اقرأ لون المنحنى</span><span class="pln">
   </span><span class="kwd">double</span><span class="pln"> g </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
   </span><span class="kwd">double</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
   curve</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln">g</span><span class="pun">,</span><span class="pln">b</span><span class="pun">);</span><span class="pln">
   </span><span class="typ">int</span><span class="pln"> symmetryCode </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextInt</span><span class="pun">();</span><span class="pln"> </span><span class="com">// اقرأ خاصية تماثل المنحنى</span><span class="pln">
   curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">symmetryCode </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
   curveData</span><span class="pun">.</span><span class="pln">points </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;&gt;();</span><span class="pln">
   </span><span class="typ">int</span><span class="pln"> pointCount </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextInt</span><span class="pun">();</span><span class="pln">  </span><span class="com">// عدد النقاط الموجودة على المنحنى</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> j </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> j </span><span class="pun">&lt;</span><span class="pln"> pointCount</span><span class="pun">;</span><span class="pln"> j</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="typ">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">        </span><span class="com">// اقرأ إحداثيات النقطة</span><span class="pln">
      </span><span class="typ">int</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
      curveData</span><span class="pun">.</span><span class="pln">points</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">));</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   newCurves</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">curve</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

curves </span><span class="pun">=</span><span class="pln"> newCurves</span><span class="pun">;</span><span class="pln">                     </span><span class="com">// اضبط بنى البيانات الجديدة</span><span class="pln">

setBackground</span><span class="pun">(</span><span class="pln">newBackgroundColor</span><span class="pun">);</span></pre>

<p>
	ينبغي أن يقرأ تابع الدْخَل البيانات الموجودة بالملف بنفس الترتيب الذي اِستخدَمه تابع الخرج أثناء كتابتها. في حين تَفِي تلك الطريقة بالغرض، لا يكون ملف البيانات الناتج مفهومًا للقارئ على الإطلاق، تمامًا كما لو كنا قد كتبنا الملف بالصيغة الثنائية binary format؛ فهو مكوّنٌ فقط من سلسلةٍ طويلةٍ من الأعداد. يجعل ذلك الملف هشًا؛ حيث سيؤدي أي تعديلٍ بسيطٍ على طريقة تمثيل البيانات ضمن إصدار أحدث من البرنامج، مثل إضافة خاصيةٍ جديدة إلى المنحنيات، إلى إهدار الملفات القديمة، إلا إذا وفَّر الملف معلومةً عن إصدار البرنامج المُستخدَم لإنشائه.
</p>

<p>
	ولهذا، قررنا الاعتماد على صيغة بياناتٍ أكثر تعقيدًا ولكنها ستُعطِي معنًى أكثر وضوحًا، فبدلًا من الاكتفاء بكتابة مجموعةٍ من الأعداد، اخترنا إضافة كلماتٍ إليها تُمثِّل معنى تلك الأعداد. نَعرِض فيما يلي مثالًا على ملف بيانات قصيرٍ نوعًا ما، لكنه يُبيّّن جميع الخاصيات المُدعَّمة حاليًا. ستتمكَّن غالبًا من فهم معناه بالكامل بمجرد قراءته:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_35" style="">
<span class="typ">SimplePaintWithFiles</span><span class="pln"> </span><span class="lit">1.0</span><span class="pln">
background </span><span class="lit">0.4</span><span class="pln"> </span><span class="lit">0.4</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln">

startcurve
  color </span><span class="lit">1</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
  symmetry </span><span class="kwd">true</span><span class="pln">
  coords </span><span class="lit">10</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
  coords </span><span class="lit">200</span><span class="pln"> </span><span class="lit">250</span><span class="pln">
  coords </span><span class="lit">300</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
endcurve

startcurve
  color </span><span class="lit">0</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
  symmetry </span><span class="kwd">false</span><span class="pln">
  coords </span><span class="lit">10</span><span class="pln"> </span><span class="lit">400</span><span class="pln">
  coords </span><span class="lit">590</span><span class="pln"> </span><span class="lit">400</span><span class="pln">
endcurve</span></pre>

<p>
	يشير السطر الأول إلى البرنامج المسؤول عن إنشاء ملف البيانات، وسيتمكَّن بذلك البرنامج من إجراء اختبارٍ بسيطٍ على الملف الذي اختار المُستخدِم فتحَه، بفحص أول كلمةٍ موجودةٍ به، ويُمكِنه بناءً على ذلك التأكُّد مما إذا كان الملف من النوع الصحيح. بالإضافة إلى ذلك، يحتوي السطر الأول على رقم إصدار 1.0، والذي ينبغي أن يتغيّر إلى أرقام إصدارٍ أعلى في حال تغيّرت صيغة الملف في الإصدارات الأحدث من البرنامج. يستطيع البرنامج بذلك أن يَفحَص رقم إصدار الملف؛ فإذا كان البرنامج قادرًا على معالجة الملفات المكتوبة وفقًا للإصدار 1.0 فقط، ووجد أن صيغة الملف مكتوبةً وفقًا لإصدارٍ آخر، مثل 1.2، يُمكنِه أن يُوضِح للمُستخدِم أن عليه استخدام نسخةٍ أحدث من البرنامج ليتمكَّن من قراءة ملف البيانات ذاك.
</p>

<p>
	يُخصِّص السطر الثاني من البرنامج لون خلفية الصورة، وهو ما يَتضَح ببساطة من خلال كلمة "background" ببداية السطر، وتُمثِّل الأعداد الثلاثة مكوِّنات اللون الأحمر والأخضر والأزرق على الترتيب؛ بينما يُمثِّل الباقي من الملف بيانات المنحنيات المرسومة بالصورة. تَفصِل الكلمتان "startcurve" و "endcurve" بيانات كل منحنًى عن الآخر؛ والتي تتكوَّن من خاصيات اللون والتماثل وكذلك إحداثيات النقاط الواقعة على المنحنى.
</p>

<p>
	يُمكِننا إنشاء هذا النوع من الملفات يدويًا وتعديلها بسهولة، لأن معناها واضح، وقد أنشأنا ملف البيانات بالأعلى بواسطة محرر نصوص لا بواسطة البرنامج. يُمكِننا إضافة المزيد من الخيارات بسهولة؛ فقد تدعَم الإصدارات الأحدث من البرنامج مثلًا خاصية "السُمْك thickness" لرسم منحنياتٍ بخطوط عرضٍ مختلفة، ويُمكِنها أيضًا دعم رسم أشكالٍ أخرى، مثل المستطيلات والأشكال البيضاوية بنفس السهولة.
</p>

<p>
	من السهل أيضًا كتابة هذا النوع من البيانات عن طريق برنامج. لنفترض مثلًا أن <code>out</code> من النوع <code>PrintWriter</code>، وأننا سنَستخدِمه لكتابة بيانات الرسمة بملف، فستُجرِي الشيفرة التالية ذلك ببساطة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_37" style="">
<span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"SimplePaintWithFiles 1.0"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// Version number.</span><span class="pln">
out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> </span><span class="str">"background "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> backgroundColor</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
        backgroundColor</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> backgroundColor</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pln"> curve </span><span class="pun">:</span><span class="pln"> curves </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">();</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"startcurve"</span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"  color "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getRed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
            curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getGreen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">color</span><span class="pun">.</span><span class="pln">getBlue</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> </span><span class="str">"  symmetry "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pln"> pt </span><span class="pun">:</span><span class="pln"> curve</span><span class="pun">.</span><span class="pln">points </span><span class="pun">)</span><span class="pln">
        out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln"> </span><span class="str">"  coords "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getX</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> pt</span><span class="pun">.</span><span class="pln">getY</span><span class="pun">()</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"endcurve"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يَستخدِم التابع <code>doSave()‎</code> -ضمن هذا البرنامج- الشيفرة بالأعلى، وهو يُشبه كثيرًا التابع الذي عرضناه في الفصل السابق. لاحِظ أن هذا التابع يَستخدِم صندوق نافذة اختيار ملف ليَسمَح للمُستخدِم باختيار ملف الخرج.
</p>

<p>
	قد تكون قراءة بيانات الملف أصعب بعض الشيء؛ حيث ينبغي للبرنامج المسؤول عن قراءة الملف أن يتعامل مع كل تلك الكلمات الزائدة الموجودة به. اخترنا كتابة ذلك البرنامج بطريقةٍ تَسمَح بتبديل ترتيب ظهور البيانات ضمن الملف، فسيَسمَح البرنامج مثلًا بتخصيص لون الخلفية بنهاية الملف بدلًا من بدايته، كما سيَسمَح بعدم تخصيصها من الأساس، وسيَستخدِم البرنامج في تلك الحالة اللون الأبيض لونًا افتراضيًا للخلفية.
</p>

<p>
	تمكَّننا من إجراء ذلك بسبب عنونة كل عنصرٍ من البيانات بكلمةٍ تَصِف معناه، واعتمد البرنامج بالتالي على تلك الكلمات لاستنتاج ما ينبغي فعله. سيَقرأ ذلك التابع ملفات البيانات التي أنشأها التابع <code>doSave()‎</code>، وسيَستخدِم الصنف <code>Scanner</code> أثناء عملية القراءة. تَعرِض الشيفرة التالية شيفرة التابع بالكامل، والمُعرَّف ببرنامج <a href="http://math.hws.edu/javanotes/source/chapter11/SimplePaintWithFiles.java" rel="external nofollow">SimplePaintWithFiles.java</a>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6810_39" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doOpen</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">FileChooser</span><span class="pln"> fileDialog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileChooser</span><span class="pun">();</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"Select File to be Opened"</span><span class="pun">);</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setInitialFileName</span><span class="pun">(</span><span class="pln">null</span><span class="pun">);</span><span class="pln">  </span><span class="com">// No file is initially selected.</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">editFile </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="typ">System</span><span class="pun">.</span><span class="pln">getProperty</span><span class="pun">(</span><span class="str">"user.home"</span><span class="pun">)));</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="pln">editFile</span><span class="pun">.</span><span class="pln">getParentFile</span><span class="pun">());</span><span class="pln">
    </span><span class="typ">File</span><span class="pln"> selectedFile </span><span class="pun">=</span><span class="pln"> fileDialog</span><span class="pun">.</span><span class="pln">showOpenDialog</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">selectedFile </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// User canceled.</span><span class="pln">
    </span><span class="typ">Scanner</span><span class="pln"> scanner</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        scanner </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="pln"> selectedFile </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"Sorry, but an error occurred\nwhile trying to open the file."</span><span class="pun">);</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">String</span><span class="pln"> programName </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">next</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> programName</span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"SimplePaintWithFiles"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"File is not a SimplePaintWithFiles data file."</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">double</span><span class="pln"> version </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">version </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1.0</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"File requires a newer version of SimplePaintWithFiles."</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">Color</span><span class="pln"> newBackgroundColor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">WHITE</span><span class="pun">;</span><span class="pln">
        </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">CurveData</span><span class="pun">&gt;</span><span class="pln"> newCurves </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">CurveData</span><span class="pun">&gt;();</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">scanner</span><span class="pun">.</span><span class="pln">hasNext</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">String</span><span class="pln"> itemName </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">next</span><span class="pun">();</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"background"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">double</span><span class="pln"> red </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                </span><span class="kwd">double</span><span class="pln"> green </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                </span><span class="kwd">double</span><span class="pln"> blue </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                newBackgroundColor </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">red</span><span class="pun">,</span><span class="pln">green</span><span class="pun">,</span><span class="pln">blue</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"startcurve"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">CurveData</span><span class="pln"> curve </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CurveData</span><span class="pun">();</span><span class="pln">
                curve</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">BLACK</span><span class="pun">;</span><span class="pln">
                curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
                curve</span><span class="pun">.</span><span class="pln">points </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Point2D</span><span class="pun">&gt;();</span><span class="pln">
                itemName </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">next</span><span class="pun">();</span><span class="pln">
                </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">!</span><span class="pln"> itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"endcurve"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"color"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="kwd">double</span><span class="pln"> r </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                        </span><span class="kwd">double</span><span class="pln"> g </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                        </span><span class="kwd">double</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                        curve</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Color</span><span class="pun">.</span><span class="pln">color</span><span class="pun">(</span><span class="pln">r</span><span class="pun">,</span><span class="pln">g</span><span class="pun">,</span><span class="pln">b</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"symmetry"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        curve</span><span class="pun">.</span><span class="pln">symmetric </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextBoolean</span><span class="pun">();</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">itemName</span><span class="pun">.</span><span class="pln">equalsIgnoreCase</span><span class="pun">(</span><span class="str">"coords"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="kwd">double</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                        </span><span class="kwd">double</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
                        curve</span><span class="pun">.</span><span class="pln">points</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Point2D</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"Unknown term in input."</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    itemName </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">next</span><span class="pun">();</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                newCurves</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">curve</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">(</span><span class="str">"Unknown term in input."</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        scanner</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
        backgroundColor </span><span class="pun">=</span><span class="pln"> newBackgroundColor</span><span class="pun">;</span><span class="pln">
        curves </span><span class="pun">=</span><span class="pln"> newCurves</span><span class="pun">;</span><span class="pln">
        redraw</span><span class="pun">();</span><span class="pln">
        editFile </span><span class="pun">=</span><span class="pln"> selectedFile</span><span class="pun">;</span><span class="pln">
        window</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"SimplePaint: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> editFile</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"Sorry, but an error occurred while\ntrying to read the data:\n"</span><span class="pln"> 
                        </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">    
</span><span class="pun">}</span></pre>

<p>
	لقد ناقشنا صيغ الملفات على هذا النحو المُفصَّل لنُحفِّزك على التفكير بمشكلة تمثيل البيانات المُعقّدة بصيغٍ يُمكِن تخزينها ضمن ملف، وسنتعرَّض لنفس المشكلة أثناء نقل البيانات عبر الشبكات. لا يُمكِننا في الواقع أن نقول أن حلًا معينًا هو الحل الصحيح لتلك المشكلة في العموم، ولكن بالطبع تُعدّ بعض الحلول أفضل من الأخرى، وسنُناقش في فصل لاحق واحدًا من أكثر الحلول شيوعًا لمشكلة تمثيل البيانات عمومًا.
</p>

<p>
	بالإضافة إلى قدرة البرنامج <code>SimplePaintWithFiles</code> على حفظ بيانات الرسوم بصيغٍ نصية، فإنه قادرٌ أيضًا على حفظها مثل ملفات صورٍ يُمكِن طباعتها أو وضعها بصفحة إنترنت على سبيل المثال. يُعدّ ذلك مثالًا عامًا على تقنيات معالجة الصور، والتي سنناقشها في جزئية لاحقة من هذه السلسلة، والتي تَستخدِم تقنيات أخرى لم نتعرَّض لها بعد.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c11/s3.html" rel="external nofollow">Section 3: Programming With Files</a> من فصل Chapter 11: Input/Output Streams, Files, and Networking من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/" rel="">مدخل إلى التعامل مع الملفات في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D9%85%D8%B9%D9%85%D9%85%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1432/" rel="">كتابة أصناف وتوابع معممة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1114/" rel="">الواجهات Interfaces في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">التعاود recursion في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1460</guid><pubDate>Mon, 31 Jan 2022 19:50:49 +0000</pubDate></item><item><title>&#x62A;&#x62D;&#x633;&#x64A;&#x646; &#x623;&#x62F;&#x627;&#x621; &#x627;&#x644;&#x62E;&#x631;&#x627;&#x626;&#x637; &#x627;&#x644;&#x645;&#x646;&#x641;&#x630;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x62A;&#x639;&#x645;&#x64A;&#x629; HashMap &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%A3%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashmap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1452/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61f79851f0047_-----.png.92525894c7127acd1cd6a37fbcc4cbc6.png" /></p>
<p>
	كتبنا تنفيذًا للواجهة <code>Map</code> باستخدام التعمية hashing في مقالة <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashing-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1416/" rel="">تنفيذ الخرائط باستخدام التعمية hashing</a>، وتوقَّعنا أن يكون ذلك التنفيذ أسرع لأن القوائم التي يبحث فيها أقصر، ولكن ما يزال ترتيب نمو order of growth ذلك التنفيذ خطّيًّا.
</p>

<p>
	إذا كان هناك عدد مقداره n من المُدْخَلات وعدد مقداره k من <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-maps-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1430/" rel="">الخرائط</a> الفرعية sub-maps، فإن حجم تلك الخرائط يُساوِي n/k في المتوسط، أي ما يزال متناسبًا مع n، ولكننا لو زدنا k مع n، سنتمكَّن من الحدِّ من حجم n/k.
</p>

<p>
	لنفترض على سبيل المثال أننا سنضاعف قيمة k في كلّ مرّةٍ تتجاوز فيها n قيمةَ k. في تلك الحالة، سيكون عدد المُدْخَلات في كلّ خريطةٍ أقلّ من 1 في المتوسط، وأقلّ من 10 على الأغلب بشرط أن تُوزِّع دالّةُ التعميةِ المفاتيحَ بشكلٍ معقول.
</p>

<p>
	إذا كان عدد المُدْخَلات في كلّ خريطةٍ فرعيّةٍ ثابتًا، سنتمكَّن من البحث فيها بزمنٍ ثابت. علاوة على ذلك، يَستغرِق حساب دالة التعمية في العموم زمنًا ثابتًا (قد يعتمد على حجم المفتاح، ولكنه لا يعتمد على عدد المفاتيح). بناءً على ما سبق، ستَستغرِق توابع <code>Map</code> الأساسية أي <code>put</code> و <code>get</code> زمنًا ثابتًا.
</p>

<p>
	سنفحص تفاصيل ذلك في التمرين التالي.
</p>

<h2>
	تمرين 9
</h2>

<p>
	وفَّرنا التصور المبدئي لجدول تعمية hash table ينمو عند الضرورة في الملف <a href="https://github.com/AllenDowney/ThinkDataStructures/blob/master/solutions/src/com/allendowney/thinkdast/MyHashMap.java" rel="external nofollow">MyHashMap.java</a>. انظر إلى بداية تعريفه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_8" style=""><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyHashMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> extends </span><span class="typ">MyBetterMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> implements </span><span class="typ">Map</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="com">// متوسط عدد المُدْخَلات المسموح بها في كل خريطة فرعية قبل إعادة حساب شيفرات التعمية</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> final </span><span class="kwd">double</span><span class="pln"> FACTOR </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1.0</span><span class="pun">;</span><span class="pln">

    </span><span class="lit">@Override</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> V put</span><span class="pun">(</span><span class="pln">K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        V oldValue </span><span class="pun">=</span><span class="pln"> super</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">

        </span><span class="com">// تأكّد مما إذا كان عدد العناصر في الخريطة الفرعية قد تجاوز الحد الأقصى</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> maps</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> FACTOR</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            rehash</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> oldValue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمتدّ الصنف <code>MyHashMap</code> من الصنف <code>MyBetterMap</code>، وبالتالي، فإنه يَرِث التوابع المُعرَّفة فيه. يعيد الصنف <code>MyHashMap</code> تعريفَ التابع <code>put</code>، فيَستدعِي أولًا التابع <code>put</code> في الصنف الأعلى superclass -أي يَستدعِي النسخة المُعرَّفة في الصنف <code>MyBetterMap</code>، ثم يَفحَص ما إذا كان عليه أن يُعيّد حساب شيفرة التعمية. يعيد التابع <code>size</code> عدد المُدْخَلات الكلية n بينما يعيد التابع <code>maps.size</code> عدد الخرائط k.
</p>

<p>
	يُحدّد الثابت FACTOR -الذي يُطلَق عليه اسم عامل الحمولة load factor- العدد الأقصى للمُدْخَلات وسطيًّا في كل خريطة فرعية. إذا تحقَّق الشرط <code>n &gt; k * FACTOR</code>، فهذا يَعنِي أن الشرط <code>n/k &gt; FACTOR</code> مُتحقِّق أيضًا، مما يَعنِي أن عدد المُدْخَلات في كلّ خريطةٍ فرعيّةٍ قد تجاوز الحد الأقصى، ولذلك، يكون علينا استدعاء التابع <code>rehash</code>.
</p>

<p>
	نفِّذ الأمر <code>ant build</code> لتصريف ملفات الشيفرة، ثم نفِّذ الأمر <code>ant MyHashMapTest</code>. ستفشل الاختبارات لأن تنفيذ التابع <code>rehash</code> يُبلِّغ عن اعتراضٍ exception، ودورك هو أن تكمل متن هذا التابع.
</p>

<p>
	إذًا أكمل متن التابع <code>rehash</code> بحيث يُجمِّع المُدْخَلات الموجودة في الجدول. بعد ذلك، عليه أن يضبُط حجم الجدول، ويضيف المُدْخَلات إليه مرةً أخرى. وفَّرنا تابعين مساعدين هما <code>MyBetterMap.makeMaps</code> و <code>MyLinearMap.getEntries</code>. ينبغي أن يُضاعِف حلك عدد الخرائط k في كل مرة يُستدعَى فيها التابع.
</p>

<h2>
	تحليل الصنف MyHashMap
</h2>

<p>
	إذا كان عدد المُدْخَلات في أكبرِ خريطةٍ فرعيّةٍ متناسبًا مع n/k، وكانت الزيادة بقيمة k متناسبةً مع n، فإن العديد من التوابع الأساسية في الصنف <code>MyBetterMap</code> تُصبِح ثابتة الزمن:
</p>

<pre class="ipsCode">    public boolean containsKey(Object target) {
        MyLinearMap&lt;K, V&gt; map = chooseMap(target);
        return map.containsKey(target);
    }

    public V get(Object key) {
        MyLinearMap&lt;K, V&gt; map = chooseMap(key);
        return map.get(key);
    }

    public V remove(Object key) {
        MyLinearMap&lt;K, V&gt; map = chooseMap(key);
        return map.remove(key);
    }
</pre>

<p>
	يَحسِب كلّ تابعٍ شيفرةَ التعمية للمفتاح، وهو ما يَستغِرق زمنًا ثابتًا، ثم يَستدعِي تابعًا على خريطةٍ فرعيّةٍ، وهو ما يَستغِرق أيضًا زمنًا ثابتًا.
</p>

<p>
	ربما الأمورُ جيدةٌ حتى الآن، ولكن ما يزال من الصعب تحليل أداء التابع الأساسي الآخر <code>put</code>، فهو يَستغرِق زمنًا ثابتًا إذا لم يضطرّ لاستدعاء التابع <code>rehash</code>، ويَستغرِق زمنًا خطيًا إذا اضطرّ لذلك. بتلك الطريقة، يكون هذا التابع مشابهًا للتابع <code>ArrayList.add</code> الذي حللنا أداءه في مقالة "تحليل زمن تشغيل القوائم المُنفَّذة باستخدام مصفوفة".
</p>

<p>
	ولنفس السبب، يتضَّح أن التابع <code>MyHashMap.put</code> يَستغرِق زمنًا ثابتًا إذا حسبنا متوسط زمنِ متتاليةٍ من الاستدعاءات. يعتمد هذا التفسير على التحليل بالتسديد amortized analysis الذي شرحناه في نفس المقالة.
</p>

<p>
	لنفترض أن العدد المبدئيَّ للخرائط الفرعية k يساوي 2، وأن عامل التحميل يساوي 1، والآن، لنفحص الزمن الذي يَستغرِقه التابع <code>put</code> لإضافة متتاليةٍ من المفاتيح. سنَعُدّ عدد المرات التي سنضطرّ خلالها لحساب شيفرة التعمية لمفتاحٍ وإضافته لخريطةٍ فرعيّةٍ، وسيكون ذلك بمنزلةِ وحدةِ عملٍ واحدة.
</p>

<p>
	سيُنفِّذ التابع <code>put</code> عند استدعائه لأوّل مرةٍ وحدة عملٍ واحدةً من وحدات العمل، وسيُنفِّذ أيضًا عند استدعائه في المرة الثانية وحدةَ عملٍ واحدةٍ. أمّا في المرة الثالثة، فسيضطرّ لإعادة حساب شيفرات التعمية، وبالتالي، سيُنفِّذ عدد 2 من وحدات العمل لكي يَحسِب شيفرات تعمية المفاتيح الموجودة بالفعل بالإضافة إلى وحدة عملٍ أخرى لحساب شيفرة تعمية المفتاح الجديد.
</p>

<p>
	والآن، أصبح حجم الجدول 4، وبالتالي، سيُنفِّذ التابع <code>put</code> عند استدعائه في المرة التالية وحدة عملٍ واحدةٍ، ولكن، في المرة التالية التي سيضطرّ خلالها لاستدعاء <code>rehash</code>، فإنه سيُنفِّذ 4 وحدات عملٍ لحساب شيفرات تعميةِ المفاتيح الموجودة ووحدة عملٍ إضافيةٍ للمفتاح الجديد.
</p>

<p>
	تُوضِّح الصورة التالية هذا النمط، حيث يظهر العمل اللازم لحساب شيفرة تعمية مفتاحٍ جديدٍ في الأسفل بينما يَظهَر العمل الإضافي كبرج.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91064" href="https://academy.hsoub.com/uploads/monthly_2022_01/001Hash_Table_Work.PNG.d91e9ed5936db4562b2f28a40eea8aab.PNG" rel="" data-fileext="PNG"><img alt="001Hash_Table_Work.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="91064" data-unique="hq6aesibm" src="https://academy.hsoub.com/uploads/monthly_2022_01/001Hash_Table_Work.PNG.d91e9ed5936db4562b2f28a40eea8aab.PNG"></a>
</p>

<p>
	إذا أنزلنا الأبراج كما تقترح الأسهم، سيملأ كلّ واحدٍ منها الفراغ الموجود قبل البرج التالي، وسنحصل على ارتفاعٍ منتظمٍ يساوي 2 وحدة عمل. يُوضِّح ذلك أن متوسط العمل لكل استدعاءٍ للتابع <code>put</code> هو 2 وحدة عمل، مما يَعنِي أنه يَستغرِق زمنًا ثابتًا في المتوسط.
</p>

<p>
	يُوضِح الرسم البياني مدى أهمية مضاعفة عدد الخرائط الفرعية k عندما نعيد حساب شيفرات التعمية؛ فلو أضفنا قيمةً ثابتةً إلى k بدلًا من مضاعفتها، ستكون الأبراج قريبة جدًا من بعضها، وستتراكم فوق بعضها، وعندها، لن نحصل على زمن ثابت.
</p>

<h2>
	مقايضات
</h2>

<p>
	رأينا أن التوابع <code>containsKey</code> و <code>get</code> و <code>remove</code> تَستغرِق زمنًا ثابتًا، وأن التابع <code>put</code> يَستغرِق زمنًا ثابتًا في المتوسط، وهذا أمرٌ رائعٌ بحق، فأداء تلك العمليات هو نفسه تقريبًا بغض النظر عن حجم الجدول.
</p>

<p>
	يَعتمِد تحليلنا لأداء تلك العمليات على نموذج معالجةٍ بسيطٍ تَستغرِق كلّ وحدةٍ عمل فيه نفس مقدار الزمن، ولكن الحواسيب الحقيقية أعقدُ من ذلك بكثير، فتبلغ أقصى سرعتها عندما تتعامل مع هياكل بيانات صغيرة بما يكفي لتُوضَع في الذاكرة المخبئية cache، وتكون أبطأ قليلًا عندما لا يتناسب حجم هياكل البيانات مع الذاكرة المخبئية ولكن مع إمكانية وضعها في الذاكرة، وتكون أبطأ بكثيرٍ إذا لم يتناسب حجم الهياكل حتى مع الذاكرة.
</p>

<p>
	هنالك مشكلةٌ أخرى، وهي أن التعمية لا تكون ذات فائدةٍ في هذا التنفيذ إذا كان المُدْخَل قيمةً وليس مفتاحًا، فالتابع <code>containsValue</code> خطّيٌّ لأنه مضطرّ للبحث في كل الخرائط الفرعية، فليس هناك طريقةٌ فعالةٌ للبحث عن قيمة ما والعثور على مفتاحها المقابل (أو مفاتيحها).
</p>

<p>
	بالإضافة إلى ما سبق، فإن بعض التوابع التي كانت تَستغرِق زمنًا ثابتًا في الصنف <code>MyLinearMap</code> قد أصبحت خطّيّةً. انظر إلى التابع التالي على سبيل المثال:
</p>

<pre class="ipsCode">    public void clear() {
        for (int i=0; i&lt;maps.size(); i++) {
            maps.get(i).clear();
        }
    }
</pre>

<p>
	يضطرّ التابع <code>clear</code> لتفريغ جميع الخرائط الفرعيّةِ التي يتناسب عددها مع n، وبالتالي، هذا التابعُ خطّيٌّ. لحسن الحظ، لا يُستخدَم هذا التابع كثيرًا، ولذا فما يزال هذا التنفيذ مقبولًا في غالبية التطبيقات.
</p>

<h2>
	تشخيص الصنف MyHashMap
</h2>

<p>
	سنفحص أولًا ما إذا كان التابع <code>MyHashMap.put</code> يستغرق زمنًا خطيًا.
</p>

<p>
	نفِّذ الأمر <code>ant build</code> لتصريف ملفات الشيفرة، ثم نفِّذ الأمر <code>ant ProfileMapPut</code>. يقيس الأمر زمن تشغيل التابع <code>HashMap.put</code> (الذي توفِّره <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">جافا</a>) مع أحجام مختلفةٍ للمشكلة، ويَعرِض زمن التشغيل مع حجم المشكلة بمقياس لوغاريتمي-لوغاريتمي. إذا كانت العملية تستغرق زمنًا ثابتًا، ينبغي أن يكون الزمن الكليُّ لعدد n من العمليات خطيًّا، ونحصل عندها على خطٍّ مستقيمٍ ميله يساوي 1. عندما شغَّلنا تلك الشيفرة، كان الميل المُقدَّر قريبًا من 1، وهو ما يتوافق مع تحليلنا للتابع. ينبغي أن تحصل على نتيجةٍ مشابهة.
</p>

<p>
	عدِّل الصنف <code>ProfileMapPut.java</code> لكي يُشخِّص التنفيذ <code>MyHashMap</code> الخاص بك وليس تنفيذ الجافا <code>HashMap</code>. شغِّل شيفرة التشخيص مرةً أخرى، وافحص ما إذا كان الميل قريبًا من 1. قد تضطرّ إلى تعديل قيم <code>startN</code> و <code>endMillis</code> لكي تعثر على نطاقٍ مناسبٍ من أحجام المشكلة يبلغ زمن تشغيلها أجزاءَ صغيرةً من الثانية، وفي نفس الوقت لا يتعدى بضعة آلاف.
</p>

<p>
	عندما شغّلنا تلك الشيفرة، وجدنا أن الميلَ يساوي 1.7 تقريبًا، مما يشير إلى أن ذلك التنفيذ لا يستغرق زمنًا ثابتًا. في الحقيقة، هو يحتوي على خطأٍ برمجيٍّ مُتعلِّقٍ بالأداء.
</p>

<p>
	عليك أن تعثر على ذلك الخطأِ وتصلحَه وتتأكَّد من أن التابع <code>put</code> يستغرق زمنًا ثابتًا كما كنا نتوقّع قبل أن تنتقل إلى القسم التالي.
</p>

<h2>
	إصلاح الصنف MyHashMap
</h2>

<p>
	تتمثل مشكلة الصنف <code>MyHashMap</code> بالتابع <code>size</code> الموروث من الصنف <code>MyBetterMap</code>. انظر إلى شيفرته فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_11" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> total </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">MyLinearMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">map</span><span class="pun">:</span><span class="pln"> maps</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            total </span><span class="pun">+=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> total</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	كما ترى يضطرّ التابع للمرور عبر جميع الخرائط الفرعية لكي يحسب الحجم الكلّيّ. نظرًا لأننا نزيد عدد الخرائط الفرعية k بزيادة عدد المُدْخَلات n، فإن k يتناسب مع n، ولذلك، يستغرق تنفيذ التابع <code>size</code> زمنًا خطّيًّا.
</p>

<p>
	يجعل ذلك التابع <code>put</code> خطّيًّا أيضًا لأنه يَستخدِم التابع <code>size</code> كما هو مُبيَّنٌ في الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_13" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> V put</span><span class="pun">(</span><span class="pln">K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        V oldValue </span><span class="pun">=</span><span class="pln"> super</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> maps</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> FACTOR</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            rehash</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> oldValue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	إذا تركنا التابع <code>size</code> خطّيًّا، فإننا نهدر كل ما فعلناه لجعل التابع <code>put</code> ثابتَ الزمن.
</p>

<p>
	لحسن الحظ، هناك حلٌّ بسيطٌ رأيناه من قبل، وهو أننا سنحتفظ بعدد المُدْخَلات ضمن متغير نسخة instance variable، وسنُحدِّثه كلما استدعينا تابعًا يُجرِي تعديلًا عليه.
</p>

<p>
	ستجد الحل في <a href="https://github.com/AllenDowney/ThinkDataStructures" rel="external nofollow">مستودع الكتاب</a> في الملف <a href="https://github.com/AllenDowney/ThinkDataStructures/blob/master/solutions/src/com/allendowney/thinkdast/MyFixedHashMap.java" rel="external nofollow">MyFixedHashMap.java</a>. انظر إلى بداية تعريف الصنف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_16" style=""><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyFixedHashMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> extends </span><span class="typ">MyHashMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> implements </span><span class="typ">Map</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> clear</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
        size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	بدلًا من تعديل الصنف <code>MyHashMap</code>، عرَّفنا صنفًا جديدًا يمتدّ منه، وأضفنا إليه متغير النسخة <code>size</code>، وضبطنا قيمتَه المبدئيَّة إلى صفر.
</p>

<p>
	أجرينا أيضًا تعديلًا بسيطًا على التابع <code>clear</code>. استدعينا أولًا نسخة <code>clear</code> المُعرَّفة في الصنف الأعلى (لتفريغ الخرائط الفرعية)، ثم حدثنا قيمة <code>size</code>.
</p>

<p>
	كانت التعديلات على التابعين <code>remove</code> و <code>put</code> أعقد قليلًا؛ لأننا عندما نستدعي نسخها في الصنف الأعلى، فإننا لا نستطيع معرفة ما إذا كان حجم الخرائط الفرعيّة قد تغيّر أم لا. تُوضِّح الشيفرة التالية الطريقة التي حاولنا بها معالجة تلك المشكلة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_18" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> V remove</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> key</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">MyLinearMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">map</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> chooseMap</span><span class="pun">(</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
        size </span><span class="pun">-=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">
        V oldValue </span><span class="pun">=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
        size </span><span class="pun">+=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> oldValue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	يَستخدِم التابعُ <code>remove</code> التابعَ <code>chooseMap</code> لكي يعثر على الخريطة المناسبة، ثم يَطرَح حجمها. بعد ذلك، يَستدعِي تابع الخريطة الفرعية <code>remove</code> الذي قد يُغيّر حجم الخريطة، حيث يعتمد ذلك على ما إذا كان قد وجد المفتاح فيها أم لا، ثم يضيف الحجم الجديد للخريطة الفرعية إلى <code>size</code>، وبالتالي تصبح القيمة النهائية صحيحة.
</p>

<p>
	أعدنا كتابة التابع <code>put</code> باتباع نفس الأسلوب:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_20" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> V put</span><span class="pun">(</span><span class="pln">K key</span><span class="pun">,</span><span class="pln"> V value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">MyLinearMap</span><span class="pun">&lt;</span><span class="pln">K</span><span class="pun">,</span><span class="pln"> V</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">map</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> chooseMap</span><span class="pun">(</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
        size </span><span class="pun">-=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">
        V oldValue </span><span class="pun">=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
        size </span><span class="pun">+=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> maps</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> FACTOR</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
            rehash</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> oldValue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	واجهنا نفس المشكلة هنا: عندما استدعينا تابع الخريطة الفرعية <code>put</code>، فإننا لا نعرف ما إذا كان قد أضاف مدخلًا جديدًا أم لا، ولذلك استخدمنا نفس الحل، أي بطرح الحجم القديم، ثم إضافة الحجم الجديد.
</p>

<p>
	والآن، أصبح تنفيذ التابع <code>size</code> بسيطًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4721_22" style=""><span class="pln">    </span><span class="kwd">public</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> size</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	ويستغرق زمنًا ثابتًا بوضوح.
</p>

<p>
	عندما شخَّصنا أداء هذا الحل، وجدنا أن الزمنَ الكلّيَّ لإضافة عدد n من المفاتيح يتناسب مع n، ويعني ذلك أن كلّ استدعاءٍ للتابع <code>put</code> يستغرق زمنًا ثابتًا كما هو مُتوقّع.
</p>

<h2>
	مخططات أصناف UML
</h2>

<p>
	كان أحد التحديات التي واجهناها عند العمل مع شيفرة هذا المقال هو وجود عددٍ كبيرٍ من الأصناف التي يعتمد بعضها على بعض. انظر إلى العلاقات بين تلك الأصناف:
</p>

<ul>
	<li>
		<code>MyLinearMap</code> يحتوي على <code>LinkedList</code> ويُنفِّذ <code>Map</code>.
	</li>
	<li>
		<code>MyBetterMap</code> يحتوي على الكثير من كائنات الصنف <code>MyLinearMap</code> ويُنفِّذ <code>Map</code>.
	</li>
	<li>
		<code>MyHashMap</code> يمتد من الصنف <code>MyBetterMap</code>، ولذلك يحتوي على كائناتٍ تنتمي إلى الصنف <code>MyLinearMap</code> ويُنفِّذ <code>Map</code>.
	</li>
	<li>
		<code>MyFixedHashMap</code> يمتد من الصنف <code>MyHashMap</code> ويُنفِّذ <code>Map</code>.
	</li>
</ul>

<p>
	لتسهيلِ فهمِ هذا النوع من العلاقات، يلجأ مهندسو البرمجيات إلى استخدامِ مخططاتِ أصنافِ UML -اختصارً إلى <a href="http://thinkdast.com/uml" rel="external nofollow">لغة النمذجة الموحدة Unified Modeling Language</a>. تُعدّ مخططات الأصناف class diagram واحدةً من المعايير الرسومية التي تُعرِّفها UML.
</p>

<p>
	يُمثَّل كل صنفٍ في تلك المخططات بصندوق، بينما تُمثَل العلاقات بين الأصناف بأسهم. تَعرِض الصورة التالية مخطط أصناف UML للأصناف المُستخدَمة في التمرين السابق، وهي مُولَّدةٌ تلقائيًّا باستخدام <a href="http://yuml.me/" rel="external nofollow">أداة yUML</a> المتاحة عبر الإنترنت.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91065" href="https://academy.hsoub.com/uploads/monthly_2022_01/002UML_Class_Diagram.PNG.30759e24197cac9f80bff3c214fcc8d9.PNG" rel="" data-fileext="PNG"><img alt="002UML_Class_Diagram.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="91065" data-unique="dqu9ne3lv" src="https://academy.hsoub.com/uploads/monthly_2022_01/002UML_Class_Diagram.PNG.30759e24197cac9f80bff3c214fcc8d9.PNG"></a>
</p>

<p>
	تُمثَّل العلاقات المختلفة بأنواعٍ مختلفة من الأسهم:
</p>

<ul>
	<li>
		تشير الأسهم ذات الرؤوس الصلبة إلى علاقات من نوع HAS-A. على سبيل المثال، تحتوي كلّ نسخةٍ من الصنف <code>MyBetterMap</code> على نسخٍ متعددةٍ من الصنف <code>MyLinearMap</code>، ولذلك هي متصلة بأسهم صلبة.
	</li>
	<li>
		تشير الأسهم ذات الرؤوس المجوفةِ والخطوطِ الصلبة إلى علاقات من نوع IS-A. على سبيل المثال، يمتد الصنف <code>MyHashMap</code> من الصنف <code>MyBetterMap</code>، ولذلك ستجدهما موصولين بسهم IS-A.
	</li>
	<li>
		تشير الأسهم ذات الرؤوس المجوّفة والخطوط المتقطّعة إلى أن الصنف يُنفِّذ واجهة. تُنفِّذ جميع الأصناف في هذا المخطط الواجهةَ Map.
	</li>
</ul>

<p>
	تُوفِّر مخططات أصناف UML طريقةً موجزةً لتوضيح الكثير من المعلومات عن مجموعةٍ من الأصناف، وتُستخدَم عادةً في مراحل التصميم للإشارة إلى تصاميمَ بديلة، وفي مراحل التنفيذ لمشاركة التصور العام عن المشروع، وفي مراحل النشر لتوثيق التصميم.
</p>

<p>
	ترجمة -بتصرّف- للفصل <a href="https://greenteapress.com/thinkdast/html/thinkdast012.html" rel="external nofollow">Chapter 11: HashMap</a> من كتاب <a href="https://greenteapress.com/thinkdast/html/index.html" rel="external nofollow">Think Data Structures: Algorithms and Information Retrieval in Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%AC%D8%B1%D8%A9-%D8%A8%D8%AD%D8%AB-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-treemap-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1453/" rel="">تحليل زمن تشغيل الخرائط المنفذة باستخدام شجرة بحث ثنائية TreeMap في جافا</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%85%D9%8A%D8%A9-hashing-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1416/" rel="">تنفيذ الخرائط باستخدام التعمية hashing في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%B2%D9%85%D9%86-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AE%D8%B1%D8%A7%D8%A6%D8%B7-%D8%A7%D9%84%D9%85%D9%86%D9%81%D8%B0%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1389/" rel="">تحليل زمن تشغيل الخرائط المنفذة باستخدام مصفوفة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%B1%D9%8A%D8%B7%D8%A9-%D9%88%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%81%D9%87%D8%B1%D8%B3-indexer-r1384/" rel="">استخدام خريطة ومجموعة لبناء مفهرس Indexer</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1452</guid><pubDate>Sun, 06 Feb 2022 15:03:00 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x645;&#x644; &#x645;&#x639; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;</title><link>https://academy.hsoub.com/programming/java/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1459/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61f821a80e652_.png.074fd45fb443c83fc80714fedd5641e6.png" /></p>

<p>
	ظل البيانات والبرامج المُخزَّنة بذاكرة الحاسوب الرئيسية main memory متوفرةً طوال فترة تشغيله، ولكن يجب الاستعانة بالملفات للإبقاء عليها بصورة دائمة؛ حيث تمثِّل مجموعةً من البيانات المُخزَّنة بقرصٍ صلب hard disk، أو بشريحة ذاكرة USB، أو بقرصٍ مضغوط CD-ROM، أو بأي نوعٍ آخر من أجهزة التخزين. تُنظَّم الملفات داخل مجلدات، وبإمكان كل مجلدٍ أن يحتوي على مجلداتٍ أخرى إلى جانب الملفات، كما يَملُك كل مجلدٍ وملف اسمًا يُعرِّف هويته.
</p>

<p>
	تستطيع البرامج عمومًا قراءة البيانات من ملفاتٍ موجودة، وكذلك إنشاء ملفاتٍ جديدة وكتابة البيانات بها، وتعتمد جافا على مجاري تدفق streams الدْخل والخرج لفعل ذلك؛ حيث تُستخدَم الكائنات المنتمية للصنف <code>FileReader</code> -وهو صنفٌ فرعيٌ من الصنف <code>Reader</code>- لقراءة البيانات المحرفية المهيأة للقراءة human-readable من ملفٍ معين؛ وتُستخدَم بالمثل الكائنات المنتمية للصنف <code>FileWriter</code> -وهو صنفٌ فرعيٌ من الصنف <code>Writer</code>- لكتابة البيانات المهيأة للقراءة بملف.
</p>

<p>
	يُستخدَم الصنفان <code>FileInputStream</code> و <code>FileOutputStream</code> للتعامل مع الملفات التي تُخزِّن البيانات بصيغةٍ مهيأة للآلة. سنناقش خلال هذا المقال الأصناف التي تتعامل مع الملفات بالصيغة المحرفية فقط، أي الصنفين <code>FileReader</code> و <code>FileWriter</code>، ولكن تذكَّر أن الصنفين <code>FileInputStream</code> و <code>FileOutputStream</code> يُستخدمان في العموم بنفس الطريقة. لاحِظ أن كلَّ تلك الأصناف مُعرَّفةٌ في حزمة <code>java.io</code>.
</p>

<h2>
	قراءة الملفات والكتابة بها
</h2>

<p>
	يستقبل <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">باني constructor</a> الصنف <code>FileReader</code> اسم ملف معين مثل معاملٍ parameter، ويُنشِئ مجرى stream مُدْخَلات لقراءة محتويات ذلك الملف؛ فإذا لم يَكُن الملف المُخصَّص موجودًا، فسيُبلِّغ الباني عن <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%AA-exceptions-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1068/" rel="">استثناء exception</a> من النوع <code>FileNotFoundException</code>. بفرض لدينا ملفٌ اسمه "data.txt"، ونريد قراءة البيانات الموجودة به، يُمكِننا إذًا إنشاء مجرى مُدْخَلات لذلك الملف بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_7" style="">
<span class="typ">FileReader</span><span class="pln"> data</span><span class="pun">;</span><span class="pln">   </span><span class="com">// 1</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileReader</span><span class="pun">(</span><span class="str">"data.txt"</span><span class="pun">);</span><span class="pln">  </span><span class="com">// أنشِئ المجرى</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">...</span><span class="pln"> </span><span class="com">// عالج الخطأ المُحتمَل</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حيث تعني [1]: صرِّح عن المُتغيّر قبل تعليمة <code>try</code>؛ وإلا سيُصبِح محليًا ضمن كتلة <code>try</code>، ولن تتمكَّن من اِستخدَامه بالبرنامج لاحقًا.
</p>

<p>
	يُمكِننا في الواقع ضبط تعليمة <code>try...catch</code> بالأعلى، بحيث تلتقط استثناءات الصنف <code>IOException</code>؛ لأن الصنف <code>FileNotFoundException</code> هو بالنهاية صنفٌ فرعيٌ subclass من الصنف <code>IOException</code>، ويُمكِننا في العموم التقاط أي خطأٍ يحدث أثناء عمليات الدخل والخرج باستخدام عبارة <code>catch</code> خُصِّصت لمعالجة الاستثناءات من النوع <code>IOException</code>.
</p>

<p>
	يُمكِننا أن نبدأ بقراءة البيانات من كائنات الصنف <code>FileReader</code> بمجرد إنشائها، ولكن نظرًا لعدم تضمُّنها سوى بعض التوابع البسيطة، فسنضطّر عادةً إلى تغليفها ضمن كائنٍ من النوع <code>Scanner</code> أو النوع <code>BufferedReader</code> أو أي صنفٍ مُغلِّف آخر. انظر <a href="https://academy.hsoub.com/programming/java/%D9%82%D9%86%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D8%AE%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%B1%D8%AC-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%AA%D9%8A-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%88%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1451/" rel="">المقال السابق</a> لمزيدٍ من المعلومات عن الصنفين <code>BufferedReader</code> و <code>Scanner</code>. تُنشِئ الشيفرة التالية كائنًا من النوع <code>BufferedReader</code> لقراءة بيانات ملفٍ اسمه "data.dat":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3525_6" style="">
<span class="typ">BufferedReader</span><span class="pln"> data</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BufferedReader</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileReader</span><span class="pun">(</span><span class="str">"data.dat"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">...</span><span class="pln"> </span><span class="com">// عالج الاستثناء</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُسهِل تغليف كائنات الصنف <code>Reader</code> بكائناتٍ تنتمي للصنف <code>BufferedReader</code> من قراءة أسطر الملفات، كما تُعزِّز خاصية التخزين المؤقت buffering من كفاءتها.
</p>

<p>
	يُمكِننا بنفس الكيفية إنشاء كائنٍ من الصنف <code>Scanner</code> لقراءة بيانات ملفٍ معين، مع أننا نلجأ عادةً في مثل تلك الحالات إلى إنشاء كائنٍ من النوع <code>File</code> مباشرةً (سنناقش ذلك بالأسفل):
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3525_8" style="">
<span class="typ">Scanner</span><span class="pln"> in</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   in </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="str">"data.dat"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">...</span><span class="pln"> </span><span class="com">// عالِج الاستثناء</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ينطبق الأمر نفسه على ملفات الخرج؛ حيث ينبغي في تلك الحالة إنشاء كائنٍ من النوع <code>FileWriter</code>، والذي نلجأ عادةً إلى تغليفه ضمن كائنٍ من النوع <code>PrintWriter</code>. قد يُبلِّغ باني الصنف <code>FileWriter</code> عن استثناء  من النوع <code>IOException</code>؛ ولهذا ينبغي أن نحيطه بتعليمة <code>try..catch</code>. لنفترض مثلًا أننا نريد كتابة بياناتٍ معينة بملفٍ اسمه "result.dat"، يُمكِننا أن نَستخدِم الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3525_10" style="">
<span class="typ">PrintWriter</span><span class="pln"> result</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   result </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileWriter</span><span class="pun">(</span><span class="str">"result.dat"</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">...</span><span class="pln"> </span><span class="com">// عالِج الاستثناء</span><span class="pln">
     
</span><span class="pun">}</span></pre>

<p>
	كما هو الحال مع الصنف <code>Scanner</code>، نُمرِّر عادةً في تلك الحالات معاملًا من النوع <code>File</code> لباني الصنف <code>PrintWriter</code>، ويؤدي ذلك إلى تغليف كائن الصنف <code>File</code> تلقائيًا ضمن كائنٍ ينتمي للصنف <code>FileWriter</code>، ثم يُنشِئ الحاسوب بعدها كائنًا من الصنف <code>PrintWriter</code>. انظر الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3525_12" style="">
<span class="typ">PrintWriter</span><span class="pln"> result</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   result </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="str">"result.dat"</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IOException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="pun">...</span><span class="pln"> </span><span class="com">// عالِج الاستثناء</span><span class="pln">
    
</span><span class="pun">}</span></pre>

<p>
	بإمكاننا أيضًا أن نُمرِّر للباني <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-string-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-class-%D9%88%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subroutine-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1017/" rel="">سلسلةً نصيةً</a> من النوع <code>String</code>، ويَعُدُّها الباني في تلك الحالة اسمًا لملف؛ بينما لو مرَّرنا سلسلةً نصيةً من النوع <code>String</code> إلى باني الصنف <code>Scanner</code>، فإنه لا يَعُدّها اسمًا لملف، وإنما يقرأ محارف السلسلة النصية ذاتها.
</p>

<p>
	في حالة عدم وجود ملفٍ اسمه "result.dat"، يُنشَئ ملفٌ جديدٌ بنفس الاسم؛ أما إذا كان موجودًا بالفعل، تَحِلّ البيانات التي يُفترض من البرنامج كتابتها بالملف محلّ محتوياته الحالية. لاحِظ أنك لن تتلقَّ أي تحذيرٍ بشأن ذلك. إذا أردت تجنُّب حدوث ذلك، عليك أن تفحص أولًا فيما إذا كان هناك ملفٌ بنفس الاسم قبل إنشاء مجرًى له كما سنناقش لاحقًا. قد يُبلِّغ باني الصنف <code>PrintWriter</code> عن استثناء من النوع <code>IOException</code>، إذا حاولت إنشاء ملفٍ داخل قرصٍ غير مسموحٍ بالكتابة به، أي لا يُمكِن تعديله.
</p>

<p>
	عندما تُنهِي عملك مع كائن من الصنف <code>PrintWriter</code>، يجب أن تستدعِي تابعه <code>flush()‎</code> بكتابة شيءٍ مثل <code>result.flush()‎</code>؛ وذلك حتى تتأكَّد من إرسال الخرج بالكامل إلى مقصده؛ وإذا نسيت أن تَستدعِيه، قد لا يظهر بالملف بعض البيانات التي أرسلتها إليه.
</p>

<p>
	بعد أن تُنهِي تعاملك مع ملفٍ معين، يُفضَّل أن تغلقه؛ بمعنى أن تُبلِّغ نظام التشغيل أنك انتهيت من اِستخدَامه. يُمكِنك أن تَستدعِي التابع <code>close()‎</code> المُعرَّف بالصنف <code>PrintWriter</code>، أو <code>BufferedReader</code>، أو <code>Scanner</code> حتى تغلق الملف. بمجرد إغلاق ملفٍ معين، لا يُمكِنك أن تقرأ بياناته، أو أن تُرسِل إليه أية بيانات، إلا إذا أعدت فتحه مرةً أخرى بإنشاء مجرًى جديد.
</p>

<p>
	قد يُبلِّغ التابع <code>close()‎</code> بغالبية أصناف المجاري -بما في ذلك الصنف <code>BufferedReader</code>- عن حدوث استثنناء من النوع <code>IOException</code>، والذي لا بُدّ من معالجته. يُعيد لحسن الحظ الصنفان <code>PrintWriter</code> و <code>Scanner</code> تعريف override ذلك التابع لمنعه من التبليغ عن مثل تلك الاستثناءات. إذا نسيت إغلاق ملفٍ معين، فإنه يُغلَق أوتوماتيكيًا بعد انتهاء البرنامج أو قد يُغلّق قبل ذلك بواسطة <strong>كانس المهملات garbage collection</strong>، ولكن لا يُفضَّل الاعتماد على ذلك.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة</strong>: يؤدي استدعاء <code>close()‎</code> إلى استدعاء <code>flush()‎</code> أتوماتيكيًا قبل اغلاق الملف.
		</p>
	</div>
</blockquote>

<p>
	يقرأ البرنامج التالي أعدادًا من ملفٍ اسمه "data.dat"، ثم يعيد كتابة نفس تلك الأعداد، ولكن بترتيبٍ معاكس إلى ملفٍ آخر اسمه "result.dat". (ملاحظة: يَفترِض البرنامج احتواء الملف "data.dat" على أعدادٍ حقيقية فقط). يَستخدِم هذا البرنامج الصنف <code>Scanner</code> لقراءة ملف الدْخَل، كما يعتمد على معالجة الاستثناءات لفحص المشكلات المحتملة. قد لا يكون البرنامج التالي مفيدًا تمامًا، ولكنه يُظهِر على الأقل أساسيات التعامل مع الملفات بوضوح:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_17" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.*;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">ArrayList</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Scanner</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ReverseFileWithScanner</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="typ">Scanner</span><span class="pln"> data</span><span class="pun">;</span><span class="pln">        </span><span class="com">// لقراءة البيانات</span><span class="pln">
        </span><span class="typ">PrintWriter</span><span class="pln"> result</span><span class="pun">;</span><span class="pln">  </span><span class="com">// مجرى محارف خرج لإرسال البيانات</span><span class="pln">

        </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Double</span><span class="pun">&gt;</span><span class="pln"> numbers</span><span class="pun">;</span><span class="pln">  </span><span class="com">// قائمة لحمل البيانات</span><span class="pln">

        numbers </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ArrayList</span><span class="pun">&lt;</span><span class="typ">Double</span><span class="pun">&gt;();</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// أنشئ مجرى دخل</span><span class="pln">
            data </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="str">"data.dat"</span><span class="pun">));</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't find file data.dat!"</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// أنهِ البرنامج بالعودة من البرنامج</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// أنشِئ مجرى خرج</span><span class="pln">
            result </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="str">"result.dat"</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't open file result.dat!"</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
            data</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">  </span><span class="com">// أغلق الملف</span><span class="pln">
            </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">        </span><span class="com">// أنهِ البرنامج</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">hasNextDouble</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// اقرأ الملف حتى نهايته</span><span class="pln">
            </span><span class="kwd">double</span><span class="pln"> inputNumber </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
            numbers</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> inputNumber </span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">// اطبع الأعداد بترتيبٍ معكوس</span><span class="pln">

        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> numbers</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()-</span><span class="lit">1</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">--)</span><span class="pln">
            result</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">i</span><span class="pun">));</span><span class="pln">

        </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Done!"</span><span class="pun">);</span><span class="pln">

        data</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
        result</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">  </span><span class="com">// end of main()</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end class ReverseFileWithScanner</span></pre>

<p>
	حيث أن [1] يقرأ البرنامج الأعداد من ملفٍ اسمه "data.dat"، ثم يكتبها إلى ملفٍ اسمه "result.dat" بترتيبٍ معكوس. لا بُدّ أن يحتوي الملف المُدْخَل على أعدادٍ حقيقيةٍ فقط.
</p>

<p>
	يتوقف البرنامج السابق عن قراءة بيانات الملف بمجرد قراءته لمُدْخَلٍ غير عددي، ولا يَعُدّه خطأً.
</p>

<p>
	كما ذكرنا بنهاية مقال <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AB%D9%86%D8%A7%D8%A1%D8%A7%D8%AA-exceptions-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1309/" rel="">الاستثناءات exceptions وتعليمة try..catch في جافا</a>، يَشيع نمط إنشاء "موردٍ resource" معينٍ أو فتحه، ثم اِستخدَامه، وغلقه، وهو نمطٌ مدعومٌ من قِبَل تعليمة <code>try..catch</code>. بحسب هذا السياق، تُعدّ الملفات بمثابة مواردٍ مثلها مثل أصناف <code>Scanner</code> و <code>PrintWriter</code> وغيرها من مجاري جافا للدْخَل والخرج. تُعرِّف جميع تلك الموارد التابع <code>close()‎</code>، ويُفضَّل طبعًا إغلاقها بعد الانتهاء من اِستخدَامها. نظرًا لأن تلك الأصناف تُنفِّذ الواجهة <code>AutoCloseable</code>، تُعدُّ جميعها مواردًا بحسب تعليمة <code>try..catch</code>، ولهذا يُمكِننا إذًا أن نَستخدِم تلك التعليمة لإغلاق الموارد أوتوماتيكيًا بمجرد انتهاء تنفيذ التعليمة دون الحاجة إلى إغلاقها يدويًا ضمن تعليمة <code>finally</code>، وذلك بفرض أنك فتحت المورد واِستخدَمته ضمن نفس تعليمة <code>try..catch</code>.
</p>

<p>
	يُعدّ البرنامج التوضيحي <a href="https://math.hws.edu/javanotes/source/chapter11/ReverseFileWithResources.java" rel="external nofollow">ReverseFileWithResources.java</a> نسخةً أخرى من المثال الذي تعرَّضنا له بالأعلى، حيث يَستخدِم البرنامج تعليمات <code>try..catch</code> لقراءة البيانات من ملف، وكتابتها إلى ملفٍ آخر. كنا قد فتحنا الملف ضمن تعليمة <code>try</code>، واِستخدَمناه ضمن تعليمة <code>try</code> أخرى بالنسخة الأصلية من البرنامج. يتطلّب في المقابل نمط المورد حدوث الخطوتين ضمن تعليمة <code>try</code> واحدة، ولهذا علينا إعادة ترتيب الشيفرة، وهو ما قد يُصعِّب معرفة مصدر الاستثناء. تتضمَّن الشيفرة التالية تعليمة <code>try..catch</code> واحدةً مسؤولةً عن فتح ملف المُدْخَلات وقرائته وغلقه أتوماتيكيًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_19" style="">
<span class="kwd">try</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="str">"data.dat"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// اقرأ الأعداد وأضِفها إلى المصفوفة</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">hasNextDouble</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="com">// اقرأ حتى تصل إلى نهايته</span><span class="pln">
        </span><span class="kwd">double</span><span class="pln"> inputNumber </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">nextDouble</span><span class="pun">();</span><span class="pln">
        numbers</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln"> inputNumber </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">FileNotFoundException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// قد يحدث إذا لم يكن الملف موجودًا أو لا يُمكِن قراءته</span><span class="pln">
    </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Can't open input file data.dat!"</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// عند حدوث خطأmain() العودة من</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُنشِئ السطر الأول المورد <code>data</code>. تتضمَّن قواعد الصيغة syntax لتعليمة <code>try</code> التصريح عن المورد وإعطائه قيمةً مبدئية داخل أقواسٍ بعد كلمة <code>try</code>. يُمكِننا أن نُصرِّح عن عدة مواردٍ يَفصِل بينها فاصلةٌ منقوطة، وتُغلَق جميعها بترتيبٍ معاكسٍ لترتيب التصريح عنها.
</p>

<h2>
	الملفات والمجلدات
</h2>

<p>
	هناك بعض الجوانب الأخرى المُتعلِّقة بأسماء الملفات، والتي لم نذكرها حتى الآن. بدايةً، إذا أردنا أن نُشير إلى ملفٍ مُحدَّد بوضوح، فلا بُدّ أن نوفِّر معلوماتٍ كافيةً عن كُلٍ من اسم الملف واسم المجلد الواقع به؛ لأنك إذا استخدمت اسم ملفٍ بسيطٍ، مثل "data.dat"، أو "result.dat"، فسيَفترِض الحاسوب وجود ذلك الملف بمجلدٍ يُعرَف باسم "المجلد الحالي current directory أو المجلد الافتراضي أو مجلد العمل"، والذي لا يُمثِل مكانًا ثابتًا، فقد يُغيِّره المُستخدِم أو حتى البرنامج. ولهذا، إذا أردت أن تُشير إلى ملفٍ معين، وكان ذلك الملف موجودًا بمجلدٍ غير المجلد الحالي، فيجب أن تشير إليه بواسطة <strong>مساره</strong>؛ أي بتوفير معلوماتٍ عن كُلٍ من اسم الملف، واسم المجلد الواقع به الملف.
</p>

<p>
	يتوفَّر نوعان من أسماء المسارات، وهو ما قد يُعقِّد الأمور قليلًا، وهما: <strong>أسماء مطلقة للمسارات absolute path names</strong>، و<strong>أسماء نسبية للمسارات relative path names</strong>؛ حيث يُحدِّد الاسم المطلق للمسار اسم ملفٍ واحدٍ فقط من بين جميع الملفات المُتاحة بالحاسوب بوضوح، بسبب احتواء اسم المسار في تلك الحالة على كافة المعلومات المُتعلِّقة باسم الملف وبالمجلد المُتضمِّن له؛ بينما يُوضِّح الاسم النسبي للمسار الكيفية التي يستطيع الحاسوب بها العثور على الملف بدءًا من المجلد الحالي.
</p>

<p>
	تختلف مع الأسف قواعد صيغة كُلٍ من أسماء الملفات والمسارات من حاسوبٍ إلى آخر إلى حدٍ ما. ألقِ نظرةً على بعض الأمثلة على ذلك:
</p>

<ul>
<li>
		<code>data.dat</code>: يُمثِّل ملفًا اسمه "data.dat" مع فرض وجوده بالمجلد الحالي. ينطبق ذلك على أي حاسوب.
	</li>
	<li>
		<code>‎/home/eck/java/examples/data.dat</code>: يُمثِّل الاسم المطلق لمسارٍ معين <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">بأنظمة تشغيل</a> UNIX، بما في ذلك <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">Linux</a> و Mac OS X، ويُشير إلى ملفٍ اسمه "data.dat"، موجودٍ بمجلدٍ اسمه "examples"، موجودٍ بدوره بمجلدٍ اسمه "java"، وهكذا.
	</li>
	<li>
		<code>‎C:\eck\java\examples\data.dat</code>: يُمثِّل الاسم المطلق لمسارٍ معيّن بأنظمة تشغيل Windows.
	</li>
	<li>
		<code>examples/data.dat</code>: يُمثِّل الاسم النسبي لمسارٍ معيّن بأنظمة تشغيل UNIX، حيث يُمثِّل "examples" اسم مجلدٍ يُفترَض وجوده بالمجلد الحالي؛ أما "data.dat" فهو اسم ملفٍ موجودٍ ضمن المجلد "examples". الاسم النسبي المكافئ لذلك المسار بأنظمة تشغيل Windows هو "examples\data.dat".
	</li>
	<li>
		<code>‎../examples/data.dat</code>: يُمثِّل الاسم النسبي لمسارٍ معيّنٍ بأنظمة تشغيل UNIX، ويَعنِي ما يلي: اذهب إلى المجلد المُتضمِّن للمجلد الحالي.، حيث ستَجِد هناك مجلدًا اسمه "examples"، اذهب إليه وستعثُر على ملفٍ اسمه "data.dat". تعني ".." عُدّ مجلدًا واحدًا للوراء. يُمثِّل "‎..\examples\data.dat" نفس المسار بأنظمة Windows.
	</li>
</ul>
<p>
	إذا كنت تتعامل مع الملفات من خلال برنامج سطر أوامر، وكانت أسماء الملفات بسيطةً نوعًا ما، ومُخزَّنة أيضًا بنفس مجلد البرنامج، فقد تسير الأمور على ما يرام. سنرى لاحقًا في هذا المقال طريقةً أفضل تَسمَح للمُستخدِم باختيار الملفات من خلال برنامج <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">واجهة مُستخدِم رسومية</a>، وهو ما يساعد على تجنُّب مشكلات أسماء المسارات تمامًا.
</p>

<p>
	تستطيع برامج جافا الإشارة إلى الاسم المطلق لمساري مجلدين مهمين، هما المجلد الحالي والمجلد الرئيسي للمُستخدِم؛ حيث تُعدّ أسماء تلك المجلدات خاصياتٍ بالنظام، ويُمكِن قراءتها باستدعاء الدوال التالية:
</p>

<ul>
<li>
		<code>System.getProperty("user.dir")‎</code>: تعيد قيمةً من النوع <code>String</code> تُمثِّل الاسم المطلق لمسار المجلد الحالي.
	</li>
	<li>
		<code>System.getProperty("user.home")‎</code>: تعيد قيمةً من النوع <code>String</code> تُمثِّل الاسم المطلق لمسار المجلد الرئيسي للمُستخدِم.
	</li>
</ul>
<p>
	تُوفِّر جافا الصنف <code>java.io.File</code>، والذي يُجنِّبنا لحسن الحظ كثيرًا من المشكلات المتعلّقة بالاختلافات بين أسماء المسارات بالمنصات المختلفة. لا يُمثِّل الكائن المنتمي لهذا الصنف أي ملفٍ تمثيلًا فعليًا، وإنما يُمثِّل "اسم الملف"، ومن الممكن أن يكون الملف الذي يُشير إليه الاسم موجودًا أو غير موجود. ينطبِق الأمر ذاته على المجلدات؛ أي يُمكِن لكائنٍ من النوع <code>File</code> أن يُمثِّل مجلدًا معينًا بنفس الكيفية التي يُمكِنه بها أن يُمثِّل ملفًا.
</p>

<p>
	يَستقبِل الباني <code>new File(String)‎</code> المُعرَّف بطبيعة الحال بالصنف <code>File</code> اسمًا لمسارٍ معين، ويُنشِئ كائنًا من النوع <code>File</code> يُشير إلى الملف الموجود بذلك المسار. يُمكِن لاسم المسار المُمرَّر أن يكون بسيطًا أو نسبيًا أو مُطلقًا. على سبيل المثال، يُنشِئ الباني <code>new File("data.dat")‎</code> كائنًا من النوع <code>File</code> يُشير إلى ملفٍ اسمه "data.dat" بالمجلد الحالي. يتوفَّر باني آخر هو <code>new File(File,String)‎</code>، والذي يَستقبِل مُعاملين: الأول هو كائنٌ من النوع <code>File</code> يُشير إلى مجلدٍ معين، أما الثاني فيُمكِنه أن يكون اسمًا لملفٍ موجودٍ ضمن المجلد المُخصَّص، أو مسارًا نسبيًا من ذاك المجلد إلى الملف المطلوب.
</p>

<p>
	تتضمَّن كائنات الصنف <code>File</code> توابع نسخ instance methods مفيدة. بفرض أن <code>file</code> هو مُتغيّرٌ من النوع <code>File</code>، يُمكِننا أن نَستخدِم أيًا من التوابع التالية:
</p>

<ul>
<li>
		<code>file.exists()‎</code>: يُعيد القيمة المنطقية <code>true</code> إذا كان الملف الذي يُخصِّصه الكائن <code>file</code> موجودًا. اِستخدِم هذا التابع إذا أردت تجنُّب كتابة بياناتك بينما تُنشِئ مجرى خرجٍ جديد على ملفٍ موجودٍ مُسبقًا. تعيد الدالة <code>file.canRead()‎</code> القيمة <code>true</code> إذا كان الملف موجودًا وكان البرنامج يَملُك صلاحيةً لقرائته؛ بينما تعيد الدالة <code>file.canWrite()‎</code> القيمة <code>true</code> إذا كان البرنامج يَملُك صلاحيةً للكتابة بذلك الملف.
	</li>
	<li>
		<code>file.isDirectory()‎</code>: يُعيد القيمة المنطقية <code>true</code> إذا كان <code>file</code> يشير إلى مجلدٍ ما؛ بينما يعيد القيمة <code>false</code> إذا كان الكائن يشير إلى ملفٍ سواءً كان ذلك الملف موجودًا أم لا.
	</li>
	<li>
		<code>file.delete()‎</code>: يحذِف الملف إذا كان موجودًا، ويعيد قيمةً منطقيةً للدلالة على نجاح عملية الحذف أو فشلها.
	</li>
	<li>
		<code>file.list()‎</code>: إذا كان <code>file</code> يشير إلى مجلد، فستُعيد الدالة مصفوفةً من النوع <code>String[]‎</code> تحتوي على أسماء الملفات الموجودة بذلك المجلد؛ أما إذا لم يَكن كذلك، فستُعيد القيمة الفارغة <code>null</code>. يعمل التابع <code>file.listFiles()‎</code> بنفس الطريقة باستثناء أنه يعيد مصفوفةً عناصرها من النوع <code>File</code> وليس <code>String</code>.
	</li>
</ul>
<p>
	يُنشِئ البرنامج التالي قائمةً بأسماء جميع الملفات الموجودة بمجلدٍ معين يُخصِّصه المُستخدِم. لاحِظ أننا اِستخدَمنا الصنف <code>Scanner</code> من أجل قراءة مُدخَلات المُستخدِم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_21" style="">
<span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">io</span><span class="pun">.</span><span class="typ">File</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> java</span><span class="pun">.</span><span class="pln">util</span><span class="pun">.</span><span class="typ">Scanner</span><span class="pun">;</span><span class="pln">

</span><span class="com">// 1</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">DirectoryList</span><span class="pln"> </span><span class="pun">{</span><span class="pln">


   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

      </span><span class="typ">String</span><span class="pln"> directoryName</span><span class="pun">;</span><span class="pln">  </span><span class="com">// اسم المجلد الذي أدخله المُستخدِم</span><span class="pln">
      </span><span class="typ">File</span><span class="pln"> directory</span><span class="pun">;</span><span class="pln">        </span><span class="com">// كائنٌ يشير إلى المجلد</span><span class="pln">
      </span><span class="typ">String</span><span class="pun">[]</span><span class="pln"> files</span><span class="pun">;</span><span class="pln">        </span><span class="com">// مصفوفة بأسماء الملفات الموجودة بالمجلد</span><span class="pln">
      </span><span class="typ">Scanner</span><span class="pln"> scanner</span><span class="pun">;</span><span class="pln">       </span><span class="com">// لقراءة سطرٍ مُدْخل واحد أدخله المُستخدِم</span><span class="pln">

      scanner </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Scanner</span><span class="pun">(</span><span class="typ">System</span><span class="pun">.</span><span class="pln">in</span><span class="pun">);</span><span class="pln">  </span><span class="com">// للقراءة من الدخل القياسي</span><span class="pln">

      </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">print</span><span class="pun">(</span><span class="str">"Enter a directory name: "</span><span class="pun">);</span><span class="pln">
      directoryName </span><span class="pun">=</span><span class="pln"> scanner</span><span class="pun">.</span><span class="pln">nextLine</span><span class="pun">().</span><span class="pln">trim</span><span class="pun">();</span><span class="pln">
      directory </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln">directoryName</span><span class="pun">);</span><span class="pln">

      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">directory</span><span class="pun">.</span><span class="pln">isDirectory</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">directory</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
             </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"There is no such directory!"</span><span class="pun">);</span><span class="pln">
          </span><span class="kwd">else</span><span class="pln">
             </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"That file is not a directory."</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          files </span><span class="pun">=</span><span class="pln"> directory</span><span class="pun">.</span><span class="typ">list</span><span class="pun">();</span><span class="pln">
          </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"Files in directory \""</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> directory </span><span class="pun">+</span><span class="pln"> </span><span class="str">"\":"</span><span class="pun">);</span><span class="pln">
          </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> files</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln">
             </span><span class="typ">System</span><span class="pun">.</span><span class="pln">out</span><span class="pun">.</span><span class="pln">println</span><span class="pun">(</span><span class="str">"   "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> files</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

   </span><span class="pun">}</span><span class="pln"> </span><span class="com">// end main()</span><span class="pln">

</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end class DirectoryList</span></pre>

<p>
	حيث تعني [1]: يَعرِض هذا البرنامج قائمةً بالملفات الموجودة بالمجلد الذي خَصَّصه المُستخدِم. يطلب البرنامج من المُستخدِم كتابة اسم المجلد، فإذا لم يكن الاسم المُدْخَل مجلدًا، يطبع البرنامج رسالةً ويُغلق.
</p>

<p>
	تتضمَّن جميع الأصناف المُستخدَمة للقراءة من الملفات والكتابة بها بُناة constructors كائن، حيث تَستقبِل تلك البُناة كائنًا من النوع <code>File</code> مثل معاملٍ. على سبيل المثال، إذا كان <code>file</code> متغيرًا من النوع <code>File</code>، وكنت تريد قراءة محارف من ذلك الملف، يُمكِنك إنشاء كائنٍ من النوع <code>FileReader</code> بكتابة <code>new FileReader(file)‎</code>.
</p>

<h2>
	صناديق نوافذ التعامل مع الملفات
</h2>

<p>
	تحتاج الكثير من البرامج إلى طريقةٍ تَسمَح بها للمُستخدِم باختيار ملفٍ معين، بحيث يُمكِنها بعد ذلك استخدام الملف المُخصَّص أثناء عمليات الدخل والخرج. إذا سَمحَنا للمُستخدِم بكتابة اسم الملف يدويًا، فإننا بذلك نفترض فهمه لطريقة عمل الملفات والمجلدات. في المقابل، إذا دعَّمنا البرنامج بواجهة مُستخدمٍ رسومية، فإننا سنُمكِّن المُستخدِم من اختيار الملف من خلال <strong>صندوق نافذة ملف file dialog box</strong>؛ حيث يُعدُّ هذا الصندوق نافذةً يستطيع البرنامج أن يفتحها إذا أراد أن يَسمَح للمُستخدِم باختيار ملفٍ معينٍ للدخل أو للخرج. توفِّر مكتبة <a href="https://academy.hsoub.com/programming/java/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A5%D9%81-%D8%A5%D9%83%D8%B3-javafx-%D8%A8%D8%B3%D9%8A%D8%B7-r1145/" rel="">جافا إف إكس JavaFX</a> الصنف <code>FileChooser</code> ضمن حزمة <code>javafx.stage</code>، والذي يُمكِنه عرض صندوق نافذة للتعامل مع الملفات، وذلك بصورةٍ مُستقلة عن المنصة التي يَعمَل عليها البرنامج.
</p>

<p>
	يَعرِض صندوق نافذة فتح ملف للمُستخدِم قائمةً بالملفات والمجلدات الفرعية الموجودة ضمن مجلدٍ معين، مما يَسمَح له بأن يختار بسهولةٍ ملفًا معينًا ضمن ذلك المجلد، كما تُمكِّنه من التنقل بين المجلدات. لا يستقبل باني الصنف <code>FileChooser</code> أية معاملات. لاحِظ أن صندوق النافذة لا يظهر تلقائيًا على الشاشة بمجرد إنشاء كائنٍ من النوع <code>FileChooser</code>، وإنما ينبغي استدعاء تابعٍ مُعرَّفٍ بهذا الكائن لإظهار صندوق النافذة. ستَستدعِي عادةً بعض توابع النسخ الأخرى المُعرَّفة بالكائن، والخاصة بضَبْط بعض خاصيات صندوق النافذة قبل أن تَعرِضها، حيث يمكن مثلًا تخصيص قيمةٍ افتراضيةٍ مثل اسم للملف.
</p>

<p>
	قد يكون لصندوق النافذة "مالك owner" يمثّل نافذةً، أي كائنًا من النوع <code>Stage</code> بمكتبة جافا إف إكس JavaFX. لا يَستطيع المُستخدِم أن يتفاعل مع النافذة المالكة حتى يُنهِي تعامله مع صندوق النافذة المفتوح؛ إما بغلقه؛ أو باختيار ملف. يُمكِننا أن نُخصِّص مالك صندوق نافذةٍ معينة بتمريره معاملًا للتابع المسؤول عن عرض صندوق النافذة؛ كما يُمكِن للمالك أن يكون فارغًا، وعندها لا يتوقف تفاعل المُستخدِم مع أي نوافذ أثناء عرض صندوق النافذة.
</p>

<p>
	هناك نوعان من صناديق نوافذ الملفات: صندوق نافذة فتح ملف وصندوق نافذة حفظ ملف؛ حيث يَسمَح الأول للمُستخدِم بتخصيص إحدى الملفات الموجودة مُسبقًا لفتحها وقراءتها بالبرنامج؛ أما الثاني فيَسمَح للمُستخدِم بتخصيص ملفٍ قد يكون موجودًا أو لا لكتابة بعض البيانات به. يُعرِّف الصنف <code>FileChooser</code> تابعي نسخة لعرض أيٍّ من الصندوقين على الشاشة. إذا كان <code>fileDialog</code> مُتغيرًا من النوع <code>FileChooser</code>، فإنه يُوفِّر التوابع التالية:
</p>

<ul>
<li>
		<code>fileDialog.showOpenDialog(window)‎</code>: يَعرِض صندوق نافذة فتح ملف على الشاشة، حيث يَستقبِل مُعاملًا يُمثِّل مالك صندوق النافذة المُفترَض فتحها. لا يعيد التابع أي قيمةٍ حتى يختار المُستخدِم ملفًا أو يَغلِق النافذة بدون اختيار أي ملف؛ حيث يعيد التابع في الحالة الأولى قيمةً من النوع <code>File</code> تُمثِّل الملف الذي اختاره المُستخدِم؛ بينما يُعيد في الحالة الثانية القيمة الفارغة <code>null</code>.
	</li>
	<li>
		<code>fileDialog.showSaveDialog(window)‎</code>: يَعرِض صندوق نافذة حفظ ملف، مالكها هو المعامل <code>window</code>. يعمل كلٌ من معامل التابع والقيمة المعادة منه بنفس أسلوب التابع <code>showOpenDialog()‎</code>؛ فإذا اختار المُستخدِم ملفًا موجودًا بالفعل، فسيسأله النظام أوتوماتيكيًا فيما إذا كان يريد بالفعل استبدال ذلك الملف، ويُمكِنك في تلك الحالة تخزين البيانات بالملف المُخصَّص دون القلق بشأن أي خطأٍ غير متوقَّع.
	</li>
	<li>
		<code>fileDialog.setTitle(title)‎</code>: يَستقبِل التابع سلسلةً نصيةً مثل معاملٍ لتخصيص عنوانٍ يَظهَر بشريط عنوان صندوق النافذة. ينبغي أن تَستدعِي هذا التابع قبل عرض صندوق النافذة.
	</li>
	<li>
		<code>fileDialog.setInitialFileName(name)‎</code>: يَضبُط اسمًا افتراضيًا يظهر بصندوق مُدْخَلات اسم الملف. لاحِظ أن المعامل هو سلسلةٌ نصية؛ فإذا كانت القيمة المُمرَّرة للمعامل فارغة، فإن صندوق الإدخال يكون بدوره فارغًا. ينبغي استدعاء هذا التابع قبل عرض صندوق النافذة المعنيّة.
	</li>
	<li>
		<code>fileDialog.setInitialDirectory(directory)‎</code>: يََضبُط أي مجلدٍ ينبغي عرضه مبدئيًا عند فتح صندوق نافذة فتح الملف. لاحِظ أن المعامل الذي يَستقبِله التابع يَكون من النوع <code>File</code>؛ فإذا كانت القيمة المُمرَّرة للمعامل فارغة، يعتمد المجلد المبدئي على الإعدادات الافتراضية للنظام (قد يكون المجلد الذي شَغلّت البرنامج منه)؛ أما إذا لم تَكن القيمة المُمرَّرة فارغة، فلا بُدّ أن تكون كائنًا من النوع <code>File</code> يُمثِّل مجلدًا لا ملفًا، وإلا سيقع خطأ. ينبغي استدعاء هذا التابع قبل عرض صندوق النافذة المعنيّة.
	</li>
</ul>
<p>
	يتضمَّن أي برنامجٍ نموذجي يتعامل مع الملفات الأمرين "افتح" و "احفظ"؛ فعندما يختار المُستخدِم ملفًا معينًا لفتحه أو لحفظ البيانات به، يُمكِننا أن نُخزِّن كائن الصنف <code>File</code> الذي يُمثِّل الملف الذي اختاره المُستخدِم بمتغير نسخة instance variable، بحيث نَستخدِمه بعد ذلك لضبط المجلد المعروض مبدئيًا، أو حتى لضبط اسم الملف بالمرة التالية التي نُنشِئ خلالها صندوق نافذة ملف.
</p>

<p>
	إذا كان <code>editFile</code> مُتغيّر نسخة يحتوي على الملف الذي اختاره المُستخدِم، وإذا لم يَكُن ذلك الملف فارغًا، فسيُعيد الاستدعاء <code>editFile.getName()‎</code> سلسلةً نصيةً من النوع <code>String</code> وتُمثِّل اسم الملف؛ في حين سيُعيد الاستدعاء <code>editFile.getParent()‎</code> كائنًا من النوع <code>File</code> يُمثِّل المجلد المُتضمِّن لذلك الملف.
</p>

<p>
	ننتقل الآن للسؤال التالي: ما الذي ينبغي فعله في حالة حدوث خطأ بينما نقرأ الملف المَعنِيّ أو نكْتُب به؟ ينبغي عمومًا التقاط ذلك الخطأ وتبليغ المُستخدِم عن حدوثه؛ فإذا كان البرنامج مُدعَّمًا بواجهة مُستخدِم رسومية، فيُعرَض عادةً للمُستخدِم صندوق نافذةٍ آخر يحتوي على رسالة الخطأ مع زر "OK" لغلق الصندوق. لم نتعرَّض لصناديق النوافذ سابقًا، ولكن يُمكِننا في العموم إنشاء كائناتٍ من الصنف <code>Alert</code> المُعرَّف بحزمة <code>javafx.scene.control</code> بسهولة لنَعرِض بعضًا من أكثر صناديق النوافذ البسيطة شيوعًا. تَعرِض الشيفرة التالية طريقة عرض صندوق نافذة يحتوي على رسالة خطأ:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_23" style="">
<span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln"> message </span><span class="pun">);</span><span class="pln">
errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span></pre>

<p>
	يُمكِننا أن نُجمِّع كل ما سبق لنكتُب البرنامج الفرعي النموذجي التالي المسؤول عن حفظ البيانات بملف، حيث يَستخدِم البرنامج الصنف <code>FileChooser</code> لاختيار الملف، والصنف <code>PrintWriter</code> لكتابة البيانات بصيغةٍ نصية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9452_25" style="">
<span class="kwd">private</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> doSave</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">FileChooser</span><span class="pln"> fileDialog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileChooser</span><span class="pun">();</span><span class="pln"> 
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">editFile </span><span class="pun">==</span><span class="pln"> null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// 1</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialFileName</span><span class="pun">(</span><span class="str">"filename.txt"</span><span class="pun">);</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="pln"> 
                </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">File</span><span class="pun">(</span><span class="pln"> </span><span class="typ">System</span><span class="pun">.</span><span class="pln">getProperty</span><span class="pun">(</span><span class="str">"user.home"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// 2</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialFileName</span><span class="pun">(</span><span class="pln">editFile</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">());</span><span class="pln">
        fileDialog</span><span class="pun">.</span><span class="pln">setInitialDirectory</span><span class="pun">(</span><span class="pln">editFile</span><span class="pun">.</span><span class="pln">getParentFile</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fileDialog</span><span class="pun">.</span><span class="pln">setTitle</span><span class="pun">(</span><span class="str">"Select File to be Saved"</span><span class="pun">);</span><span class="pln">
    </span><span class="typ">File</span><span class="pln"> selectedFile </span><span class="pun">=</span><span class="pln"> fileDialog</span><span class="pun">.</span><span class="pln">showSaveDialog</span><span class="pun">(</span><span class="pln">mainWindow</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> selectedFile </span><span class="pun">==</span><span class="pln"> null </span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">  </span><span class="com">// لم يختر المستخدم ملفًا</span><span class="pln">
    </span><span class="com">// 3</span><span class="pln">
    </span><span class="typ">PrintWriter</span><span class="pln"> out</span><span class="pun">;</span><span class="pln"> 
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">FileWriter</span><span class="pln"> stream </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileWriter</span><span class="pun">(</span><span class="pln">selectedFile</span><span class="pun">);</span><span class="pln"> 
        out </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PrintWriter</span><span class="pun">(</span><span class="pln"> stream </span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="com">// لا يملُك المُستخدِم على الأغلب صلاحيةً للكتابة بالملف</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"Sorry, but an error occurred while\n"</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
                trying to open the file </span><span class="kwd">for</span><span class="pln"> output</span><span class="pun">.</span><span class="str">");</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="pun">.</span><span class="pln">
           </span><span class="pun">.</span><span class="pln">   </span><span class="com">// ‫اكتب النص إلى الملف باستخدام PrintWriter</span><span class="pln">
               </span><span class="com">//WRITE TEXT TO THE FILE, using the PrintWriter</span><span class="pln">
           </span><span class="pun">.</span><span class="pln">
        out</span><span class="pun">.</span><span class="pln">flush</span><span class="pun">();</span><span class="pln"> </span><span class="com">// ‫هل هي ضرورية؟ ربما ستُنجز من خلال الأمر ()out.close </span><span class="pln">
        out</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">out</span><span class="pun">.</span><span class="pln">checkError</span><span class="pun">())</span><span class="pln">   </span><span class="com">// افحص الأخطاء المحتملة</span><span class="pln">
            </span><span class="pun">(</span><span class="pln">need to check </span><span class="kwd">for</span><span class="pln"> errors in </span><span class="typ">PrintWriter</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">IOException</span><span class="pun">(</span><span class="str">"Error check failed."</span><span class="pun">);</span><span class="pln">
        editFile </span><span class="pun">=</span><span class="pln"> selectedFile</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Exception</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Alert</span><span class="pln"> errorAlert </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Alert</span><span class="pun">(</span><span class="typ">Alert</span><span class="pun">.</span><span class="typ">AlertType</span><span class="pun">.</span><span class="pln">ERROR</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"Sorry, but an error occurred while\n"</span><span class="pln"> </span><span class="pun">+</span><span class="pln">
                </span><span class="str">"trying to write data to the file."</span><span class="pun">);</span><span class="pln">
        errorAlert</span><span class="pun">.</span><span class="pln">showAndWait</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">    
</span><span class="pun">}</span></pre>

<p>
	حيث:
</p>

<ul>
<li>
		[1]: لم يُعدَّل أي ملف. اضبط اسم الملف إلى "filename.txt" واسم المجلد إلى المجلد الرئيسي للمُستخدِم.
	</li>
	<li>
		[2]: استرجع اسم الملف والمجلد لصندوق النافذة من الملف الذي يُعدِّله المُستخدِم حاليًا.
	</li>
	<li>
		[3]: ملاحظة: لقد اختار المُستخدِم ملفًا، وفي حال وجود ملفٍ بنفس الاسم، فإنه قد أكّد بالفعل على رغبته بحذف الملف الموجود.
	</li>
</ul>
<p>
	يُمكِننا تطبيق نفس الفكرة على الملفات غير النصية، مع استخدام نوعٍ مختلف من مجاري الخرج.
</p>

<p>
	تَعمَل قراءة البيانات من ملفٍ معينٍ بنفس الأسلوب، ولهذا لن نناقش التابع المكافئ <code>doOpen()‎</code>. يُمكِنك مع ذلك الإطلاع على البرنامج التوضيحي <a href="http://math.hws.edu/javanotes/source/chapter11/TrivialEdit.java" rel="external nofollow">TrivialEdit.java</a>، حيث ستَجِد برامجًا فرعية subroutines مسؤولةً عن فتح الملفات النصية وحفظها، كما يَسمَح هذا البرنامج للمُستخدِم بتعديل بعض الملفات النصية الصغيرة، وتعديل البرامج الفرعية المُعرَّفة ضمنه، وإعادة اِستخدَامها ضمن برامج واجهات مُستخدِم رسومية <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1070/" rel="">GUI</a> أخرى تتعامل مع الملفات.
</p>

<p>
	ترجمة -بتصرّف- للقسم <a href="http://math.hws.edu/javanotes/c11/s2.html" rel="external nofollow">Section 2: Files</a> من فصل Chapter 11: Input/Output Streams, Files, and Networking من كتاب <a href="http://math.hws.edu/javanotes/index.html" rel="external nofollow">Introduction to Programming Using Java</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/java/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1460/" rel="">معالجة الملفات في جافا</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/java/%D9%82%D9%86%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D8%AF%D8%AE%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%B1%D8%AC-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%AA%D9%8A-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%88%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1451/" rel="">قنوات الدخل والخرج وعمليتي القراءة والكتابة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-%D9%85%D8%B9%D9%85%D9%85%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1432/" rel="">كتابة أصناف وتوابع معممة في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1114/" rel="">الواجهات Interfaces في جافا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%88%D8%AF-recursion-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1361/" rel="">التعاود recursion في جافا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1459</guid><pubDate>Fri, 04 Feb 2022 16:05:01 +0000</pubDate></item></channel></rss>
