Javaالإنترفيس Runnable
في جافا
- مقدمة
- دوال الإنترفيس
Runnable
- أمثلة شاملة
- أهمية الإنترفيس
Runnable
في التطبيقات الفعلية
مقدمة
المشكلة الوحيدة التي قد تواجهها عند إنشاء الـ Thread من خلال وراثة الكلاس Thread
هي أن الكلاس يصبح غير قادر على أن يرث من كلاس آخر, لأنه لا يمكن للكلاس أن يفعل extends
لأكثر من كلاس.
لحل مشكلة تعدد الوراثة, يمكنك جعل الكلاس يطبق الإنترفيس Runnable
و بداخله نقوم بإنشاء كائن من الكلاس Thread
و نربط به.
بناؤه
public interface Runnable
خطوات تطبيق الإنترفيس Runnable
الكلاس الذي يطبق الإنترفيس Runnable
عليه القيام بالخطوات التالية:
- تطبيق الإنترفيس
Runnable
, أي أن يفعل لهimplements
. - كتابة محتوى الدالة
run()
, أي أن يفعل لها Override و يضع فيها جميع الأوامر التي يريدها أن تتنفذ عند تشغيل كائن الـThread
.
مثال
public class MyRunnable implements Runnable { public void run(){ // Thread من كائن من الكلاس start() هنا تضع الأوامر التي تريد تنفيذها عند إستدعاء الدالة } }
خطوات إنشاء كائن من الكلاس Thread
و ربطه بالكلاس الذي يطبق الإنترفيس Runnable
لإنشاء كائن من الكلاس الذي يطبق الإنترفيس Runnable
و تنفيذ أوامره بشكل متوازي مع باقي الأوامر الموجودة في البرنامج, عليك إتباع الخطوات التالية:
- إنشاء كائن من الكلاس الذي يرث من الإنترفيس
Runnable
. - إنشاء كائن من الكلاس
Thread
, و تمرير كائن من الكلاس الذي يطبق الإنترفيسRunnable
كـ Argument له. - إستدعاء الدالة
start()
لتشغيله. - بعدها يمكنك التعامل معه كـ
Thread
عادي و التحكم بطريقة تنفيذ أوامره كما تشاء.
مثال
public class MainRunnable { public static void main(Strigns[] args)){ MyRunnable mr = new MyRunnable(); // MyRunnable هنا قمنا بإنشاء كائن من الكلاس الذي يطبق الإنترفيس Thread t = new Thread(mr); // mr و ربطه بالكائن Thread هنا قمنا بإنشاء كائن من الكلاس t.start(); // start() بواسطة الدالة run() هنا قمنا بتشغيل أوامر الدالة } }
دوال الإنترفيس Runnable
الإنترفيس Runnable
يملك الدالة التالية فقط.
public void run()
الكلاس الذي يطبق الإنترفيس Runnable
يجب أن يفعل لها Override, و يضع بداخلها الأوامر التي يريدها أن تتنفذ عند تشغيل كائن الـ Thread
.
لتنفيذ أوامر الدالة run()
بشكل متوازي مع باقي الأوامر الموجودة في البرنامج, عليك إستدعاء الدالة start()
من كائن الـ Thread
المرتبط بالكلاس الذي يطبق الإنترفيس Runnable
, و الذي يملك الدالة run()
.
تذكر: إستدعاء الدالة run()
بشكل مباشر لا يجعلها تعمل بشكل متوازي مع البرنامج.
أمثلة شاملة
هذه نفس الأمثلة التي وضعناها في شرح الكلاس Thread
من حيث الأفكار, مع بعض التعديلات لجعل الكلاس يطبق الإنترفيس Runnable
بدل أن يرث من الكلاس Thread
.
المثال الأول
في المثال التالي قمنا بإنشاء برنامج يعرض الوقت الحالي طالما أن البرنامج شغال.
في البداية قمنا بإنشاء كلاس إسمه RealTime
يطبق الإنترفيس Runnable
.
بعدها فعلنا Override للدالة run()
لجعلها تطبع الوقت الحالي.
ثم قمنا بإنشاء كلاس إسمه Main
لتجربة هذا الـ Thread.
في الكلاس Main
قمنا بإنشاء كائن من الكلاس Thread
ربطناه بالكلاس RealTime
, ثم قمنا بتشغيله بواسطة الدالة start()
.
إذاً عند تشغيل هذا البرنامج سيتم عرض الوقت الحالي طالما أن البرنامج شغال.
import java.util.Date; // Date هنا قمنا باستدعاء الكلاس public class RealTime implements Runnable { public void run() { // true لا ترجع isInterrupted() طالما أن الدالة while(!Thread.currentThread().isInterrupted()) { // سيتم طباعة الوقت الحالي System.out.printf("Current time: %tr \n", new Date()); // لثانية واحدة Thread بعدها سيتم إيقاف كائن الـ try { Thread.sleep(1000); } catch(Exception e) { System.out.println(e.getMessage()); } } } }
public class Main { public static void main(String[] args) { // rt إسمه RealTime هنا قمنا بإنشاء كائن من الكلاس RealTime rt = new RealTime(); // rt و ربطناه بالكائن t إسمه Thread هنا قمنا بإنشاء كائن من الكلاس Thread t = new Thread(rt); // لعرض الوقت t هنا قمنا بتشغيل الكائن t.start(); } }
عند تشغيل البرنامج, سيتم عرض الوقت الحالي كل ثانية كالتالي.
Current time: 09:02:15 AM Current time: 09:02:16 AM Current time: 09:02:17 AM Current time: 09:02:18 AM Current time: 09:02:19 AM Current time: 09:02:20 AM Current time: 09:02:21 AM Current time: 09:02:22 AM Current time: 09:02:23 AM Current time: 09:02:24 AM Current time: 09:02:25 AM Current time: 09:02:26 AM Current time: 09:02:27 AM ....
المثال الثاني
المثال التالي عبارة عن برنامج يقوم بإختبار قدرة المستخدم في العمليات الحسابية, فهو يقوم بخلق عمليات جمع عشوائية خلال مدة معينة, و إنتظار المستخدم للإجابة عليها, و في الأخير سيتم عرض النتيجة النهائية له.
في المثال التالي قمنا بإنشاء كلاس إسمه ExamTimer
يطبق الإنترفيس Runnable
.
بعدها فعلنا Override للدالة run()
حتى نجعل أي كائن من الكلاس ExamTimer
ينتظر مدة 20 ثانية بعد تشغيله, ثم يتوقف مباشرةً عن العمل.
ثم قمنا بإنشاء كلاس إسمه Main
, و الذي سيستخدم الكلاس ExamTimer
كمؤقت.
في الكلاس Main
فعلنا الأشياء التالية:
- أنشأنا كائن من الكلاس
ExamTimer
إسمهet
. - أنشأنا كائن من الكلاس
Thread
إسمهt
و ربطناه بالكائنet
. - قمنا بتشغيل الكائن
t
بواسطة الدالةstart()
. - قمنا بتعريف المتغيرات
num1
وnum2
لتخزين الأرقام العشوائية التي سيتم توليدها في البرنامج. - قمنا بتعريف المتغير
userAnswer
لتخزين العدد الذي سيدخله المستخدم في كل مرة. - قمنا بتعريف المتغيرات
operationsCounter
,correctAnswersCounter
وwrongAnswersCounter
كعدادات في البرنامج.
operationsCounter
: لتخزين عدد العمليات التي تظهر أمام المستخدم.
correctAnswersCounter
: لتخزين عدد إجابات المستخدم الصحيحة.
wrongAnswersCounter
: لتخزين عدد إجابات المستخدم الخاطئة. - قمنا بتعريف حلقة
while
تستمر في توليد أرقام العشوائية, إعداد عمليات جمع و إنتظار المستخدم لمعرفة الإجابة إلخ.. بالإعتماد على الدالةisAlive()
التي تبقي الحلقة تعيد تنفيذ الأوامر الموجودة فيها طالما أن مدة الإنتظار المحددة للكائنt
غير منتهية بعد.
public class ExamTimer implements Runnable { public void run() { // لثانية واحدة Thread بعدها سيتم إيقاف كائن الـ try { Thread.sleep(20000); } catch(Exception e) { System.out.println(e.getMessage()); } } }
import java.util.Scanner; // Scanner هنا قمنا باستدعاء الكلاس public class Main { public static void main(String[] args) { // و الذي سنستخدمه لإدخال بيانات من المستخدم input إسمه Scanner هنا قمنا بإنشاء كائن من الكلاس Scanner input = new Scanner(System.in); // et إسمه ExamTimer هنا قمنا بإنشاء كائن من الكلاس ExamTimer et = new ExamTimer(); // et و ربطناه بالكائن t إسمه Thread هنا قمنا بإنشاء كائن من الكلاس Thread t = new Thread(et); int num1; // سنستخدم هذا المتغير لتخزين أول رقم عشوائي يظهر في عملية الجمع int num2; // سنستخدم هذا المتغير لتخزين ثاني رقم عشوائي يظهر في عملية الجمع int userAnswer; // سنستخدم هذا المتغير لتخزين العدد الذي سيدخله المستخدم للإجابة على عمليأت الجمع int operationsCounter = 0; // سنخزن عدد العمليات الحسابية التي ستظهر عند تشغيل البرنامج فيه int correctAnswersCounter = 0; // سنخزن عدد الإجابات الصحيحة في هذا المتغير int wrongAnswersCounter = 0; // سنخزن عدد الإجابات الخطئ من ي هذا المتغير // الأمر الذي سيجعله في حالة إنتظار مدة 20 ثانية فقط و بعدها سيتوقف كلياً t هنا قمنا بتشغيل الكائن t.start(); System.out.println("---------- Quiz ---------"); // طالما أن مدة العشرين ثانية لم تنقضي بعد سيستمر في تنفيذ الأوامر الموجودة في هذه الحلقة while(t.isAlive()) { num1 = (int)(Math.random()*10); // num1 سيتم تخزين رقم عشوائي بين 1 و 9 في المتغير num2 = (int)(Math.random()*10); // num2 سيتم تخزين رقم عشوائي بين 1 و 9 في المتغير System.out.print(num1+" + "+num2+" = "); // num2 و num1 هنا سيطلب من المستخدم معرفة ناتج جمع العددين userAnswer = input.nextInt(); // هنا سيتم إنتظار المستخدم لإدخال الجواب if(userAnswer == num1+num2) // إذا كانت إجابته صحيحة, سيتم إضافة عدد الإجابات الصحيحة واحداً correctAnswersCounter++; else // إذا كانت إجابته خاطئة, سيتم إضافة عدد الإجابات الخطأ واحداً wrongAnswersCounter++; operationsCounter++; // في الأخير سيتم إضافة عدد عمليات الجمع واحداً } System.out.println("Time end..\n"); // بعد إنتهاء مدة العشرين ثانية سيتم طباعة عدد عمليات الجمع, عدد الأجوبة الصحيحة و عدد الأجوبة الخاطئة System.out.println("--------- Result --------"); System.out.println("Number of operations: " +operationsCounter); System.out.println("Number of correct answers: " +correctAnswersCounter); System.out.println("Number of wrong answers: " +wrongAnswersCounter); } }
عند تشغيل البرنامج, سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.
الأرقام التي قمنا بتعليمها باللون الأصفر هي التي قمنا بإدخالها عند تجربة البرنامج.
0 + 8 = 8
5 + 7 = 11
8 + 8 = 16
6 + 6 = 12
3 + 9 = 12
7 + 4 = 14
9 + 4 = 13
Time end..
--------- Result --------
Number of operations: 7
Number of correct answers: 5
Number of wrong answers: 2
المثال الثالث
في المثال التالي قمنا تشغيل أكثر من كائن Runnable
في وقت واحد. الهدف هنا جعلك تدرك أن الأوامر لا تتنفذ فعلياً في وقت واحد لكنها تتنفذ بسرعة عالية جداً تجعل المستخدم يظن أنها تحدث في وقت واحد.
في البداية قمنا بإنشاء كلاس إسمه MyRunnable
يطبق الإنترفيس Runnable
.
بعدها فعلنا Override للدالة run()
حتى نجعل أي كائن من الكلاس MyRunnable
يطبع إسمه كل ثانية.
ثم قمنا بإنشاء كلاس إسمه Main
, و الذي قمنا فيه بإنشاء ثلاث كائنات من الكلاس MyRunnable
, و ربطهم بثلاث كائنات من الكلاس Thread
و تشغيلهم مع بعض.
public class MyRunnable implements Runnable { private String name; // سنقوم بإعطائه إسم MyRunnable عند إنشاء كائن من الكلاس public MyRunnable(String name) { this.name = name; } public void run() { // true لا ترجع isInterrupted() طالما أن الدالة while (!Thread.currentThread().isInterrupted()) { // الذي قام بإستدعائها Thread سيتم طباعة إسم كائن الـ System.out.println(name); // بعدها سيتم إيقافه مدة ثانية واحدة try { Thread.sleep(1000); } catch(Exception e) { System.out.println(e.getMessage()); } } } }
public class Main { public static void main(String[] args) { // مع إعطاء كل واحد منهم إسم مختلف ( mr1 - mr2 - mr3 ) إسمهم MyRunnable هنا قمنا بإنشاء ثلاث كائنات من الكلاس MyRunnable mr1 = new MyRunnable("Runnable 1"); MyRunnable mr2 = new MyRunnable("Runnable 2"); MyRunnable mr3 = new MyRunnable("Runnable 3"); // ( mr1 - mr2 - mr3 ) و ربطناههم بالكائنات ( t1 - t2 - t3 ) إسمهم Thread هنا قمنا بإنشاء ثلاث كائنات من الكلاس Thread t1 = new Thread(mr1); Thread t2 = new Thread(mr2); Thread t3 = new Thread(mr3); // كل ثانية ( t1 - t2 - t3 ) هنا قمنا بتشغيل جميع الكائنات, و بالتالي سيتم طباعة أسماء الكائنات t1.start(); t2.start(); t3.start(); } }
عند تشغيل البرنامج, نلاحظ أنه يطبع ثلاثة أسطر كل ثانية .
Runnable 1 Runnable 3 Runnable 2 Runnable 3 Runnable 2 Runnable 1 Runnable 2 Runnable 3 Runnable 1 Runnable 2 Runnable 3 Runnable 1 Runnable 1 Runnable 2 Runnable 3 ...
المثال الرابع
في المثال التالي قمنا بإنشاء برنامج يظهر أمام المستخدم لائحة فيها أربع خيارات: تشغيل صوت التنبيه, إيقافه, إعادته للعمل, إيقافه نهائياً.
ميزة هذا البرنامج أنه يمكن تشغيل الإنذار أو إيقافه في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.
في البداية قمنا بإنشاء برنامج يظهر أمام المستخدم لائحة فيها أربع خيارات: تشغيل إنذار, إيقافه, إعادته للعمل, إيقافه نهائياً.
ميزة هذا البرنامج يمكنه تشغيل الإنذار في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.
في البداية قمنا بإنشاء كلاس إسمه Alert
يطبق الإنترفيس Runnable
.
بعدها فعلنا Override للدالة run()
حتى نجعل أي كائن من الكلاس Alert
يصدر صوت تنبيه عند تشغيله.
ثم قمنا بإنشاء كلاس إسمه Main
لتجربة الكلاس Alert
.
عند تشغيل البرنامج ظهر لك أربع خيارات: تشغيل صوت التنبيه, إيقافه, إعادته للعمل, إيقافه نهائياً.
ستلاحظ أن البرنامج قادر على تشغيل صوت التنبيه في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.
إنتبه: إرفع صوت الحاسوب إلا أعلا مستوى حتى تسمع صوت التنبيه عند تشغيله.
import java.awt.Toolkit; // لأننا سنستخدمه للحصول على صوت التنبيه Toolkit هنا قمنا باستدعاء الكلاس public class Alert implements Runnable { public void run() { // true ترجع isInterrupted() طالما أن الدالة while (!Thread.isInterrupted()) { // سيتم إصدار صوت المنبه كل ثانية try { Toolkit.getDefaultToolkit().beep(); Thread.sleep(1000); } catch(Exception e) { System.out.println(e.getMessage()); } } } }
import java.util.Scanner; // Scanner هنا قمنا باستدعاء الكلاس public class Main { public static void main(String[] args) { // و الذي سنستخدمه لإدخال بيانات من المستخدم input إسمه Scanner هنا قمنا بإنشاء كائن من الكلاس Scanner input = new Scanner(System.in); // سنستخدم هذا المتغير لتخزين العدد الذي سيدخله المستخدم في كل مرة int userInput; // alert إسمه Alert هنا قمنا بإنشاء كائن من الكلاس Alert alert = new Alert(); // alert و ربطناه بالكائن alertThread إسمه Thread هنا قمنا بإنشاء كائن من الكلاس Thread alertThread = new Thread(alert); // حتى نضمن أن لا يتم إستدعاءها مرتين start() سنستخدم هذا المتغير كشرط أساسي في الدالة boolean isStarted = false; // هنا قمنا بطباعة شكل لائحة من الخيارات أمام المستخدم System.out.println("----- Alert Menu ------\n" +"| Enter (1) to Start |\n" +"| Enter (2) to Pause |\n" +"| Enter (3) to Resume |\n" +"| Enter (4) to Stop |\n" +"-----------------------"); // interrupt() بواسطة الدالة alertThread() هذه الحلقة تستمر في تنفيذ ما في داخلها طالما أنه لم يتم إيقاف الكائن while(!alertThread.isInterrupted()) { // هنا سيتم إنتظار المستخدم ليدخل رقم الخيار الذي يريد System.out.print("User input >> "); userInput = input.nextInt(); // هنا سيتم فحص القيمة التي أدخلها المستخدم switch(userInput) { // في حال قام بإدخال الرقم 1 سيتم تشغيل التنبيه case 1: // أكثر من مرة واحدة start() الشرط التالي يضمن أن لا يقوم المستخدم باستدعاء الدالة if (isStarted == false) { alertThread.start(); isStarted = true; } // بدلاً منها resume() بعد إستدعائها للمرة للأولى, سيتم إستدعاء الدالة start() هنا قلنا أن أي إستدعاء للدالة else { alertThread.resume(); } break; // في حال قام بإدخال الرقم 2 سيتم إيقاف التنبيه مع إمكانية تشغيله من جديد case 2: alertThread.suspend(); break; // في حال قام بإدخال الرقم 3 سيتم إعادة التنبيه للعمل من جديد case 3: alertThread.resume(); break; // في حال قام بإدخال الرقم 4 سيتم إيقاف التنبيه مع عدم إمكانية تشغيله من جديد case 4: alertThread.interrupt(); alertThread.stop(); break; // في حال قام بإدخال رقم أكبر من 4 أو أصغر من واحد سيتم إظهار خطأ له default: System.out.println("Option <"+userInput+"> Not Found!"); break; } } } }
قم بتشغيل البرنامج و أدخل نفس القيم التي قمنا بتعليمها باللون الأصفر لتلاحظ كيف يعمل.
لا تنسى رفع صوت حاسوبك إلى أعلا مستوى حتى تسمع صوت التنبيه و هو يعمل.
| Enter (1) to Start |
| Enter (2) to Pause |
| Enter (3) to Resume |
| Enter (4) to Stop |
-----------------------
User input >> 1
User input >> 2
User input >> 3
User input >> 2
User input >> 3
User input >> 4
أهمية الإنترفيس Runnable
في التطبيقات الفعلية
أهم نقطة في تطبيق الإنترفيس Runnable
هي أنه يجعلك قادراً على بناء كلاس يعمل كـ Thread و بنفس الوقت يرث من كلاس آخر.
مثال
public class MyRunnable extends AnOtherClass implements Runnable { public void run(){ // Thread من كائن من الكلاس start() هنا تضع الأوامر التي تريد تنفيذها عند إستدعاء الدالة } }
هذا الأسلوب مهم جداً و قد تحتاجه عند بناء تطبيق فيه واجهة مستخدم (GUI) و يعمل بشكل متوازي مع باقي الأوامر الموجودة في البرنامج.
لأنه عند بناء تطبيق فيه GUI عليك أن تفعل extends
من كلاس محدد مثل الكلاس JFrame
, و تفعل implements
للإنترفيس Runnable
.
كمثال بسيط, هذا الأسلوب يجعلك قادراً على بناء تطبيق فيه واجهة مستخدم و يعرض لك الوقت و التاريخ.
لا تقلق ستتعلم هذه الأمور في دروس لاحقة.