• 0
ahmed.o.mohamed

الـ Collections في الجافا

سؤال

السلام عليكم و رحمة الله و بركاته

 

البداية ستكون مع تعريف بسيط و مختصر لهياكل البيانات بشكل عام ثم نأتي بعد ذلك إلى الـ Collections و أهميتها في البرمجة لننتقل إلى التغيرات الجديدة التي أضافها الـ Collections FrameWork من خلال استعراض الـ Hierarchy الجديد و شرح دور مختلف الـ interfaces الموجودة مثل Map, Set, List, SortedSet, SotedMap و الـ Iterators المرافقة لكل interface بالإضافة إلى شرح مفصل لمختلف الدوال الموجودة في كل واحدة مع أمثلة في نهاية كل فقرة.
بعد ذلك ننتقل إلى الـ views و أنواعها الموجودة و كيفية استخدامها.
الفقرة الموالية تتعلق بترتيب الـ Collections مع توضيح الفرق بين Comparable و Comparator و متى نستخدم كل واحدة منهما مع مثال توضيحي شامل.
الختام سيكون مع الـ algorithmes التي تُمكن من التعامل مع الـ collections من خلال وصف دقيق لكل خورارزمية على حدة بالإضافة إلى كيفية الحصول على نسخة multi-thread أو unmodifiable لـ list معينة

 

تعريف مختصر لهياكل البيانات

عبارة عن طريقة خاصة لتخزين وتنظيم البيانات على شكل array, list, stack .. etc, هذه الخصوصية تكمن في كمية الذاكرة المستخدمة لتخزين البيانات بالإضافة إلى الوقت المستغرق لإجراء بعض العمليات على تلك البيانات. تختلف أنواع هياكل البيانات باختلاف التطبيقات البرمجية أو نوعية الاستخدام, كل حسب ما يناسبه. فمثلا نجد أن الــ B-Tree تناسب التطبيقات الخاصة بقواعد البيانات بينما نجد الــ Hash Table يُستخدم بكثرة في برمجة الــ compilers خصوصا عند البحث عن الــ identifiers.

 

ما هي الــ Collections ؟

تقوم الــ collections بإدارة و تسيير مجموعة من الكائنات ذات نوع معين أو يمكننا القول بأنها عبارة عن كائن يمكنه تخزين بيانات من نوع آخر. في أولى إصدارات الجافا, اقتصر تمثيل الــ collections في Vector, Stack, Hashtable, Bitset ثم ظهر بعد ذلك الــ Collections frameWork في Java 1.2 الذي حافظ على المبادئ الأساسية لهياكل البيانات و أضاف العديد من التعديلات و التحسينات بحيث تم تصميم تلك الهياكل على شكل شجري محكم التنسيق.

يمكن للــ collections تخزين العناصر أيا كان نوعها بشرط أن تكون كائنات. يمكن مثلا أن تكون String, Integer, Float, Manager أو أي كائن آخر .. لكن مع ذلك من الخطر جدا استخدام collection تحتوي على أنواع مختلفة من الكائنات لأنك ستحتاج دوما إلى استخدام الــ casting أو instanceof من أجل تحديد صنف  الكائن. بالتالي يُستحسن دائما تخزين كائنات من نفس النوع في الــ collection الواحدة.

عند إدراج عنصر معين في الــ collection يتم الاكتفاء بتخزين الــ reference الخاص بالــ object و بالتالي يمكننا أحيانا إدراج الــ null reference داخل collection.

 

مالجديد في Collections FrameWork ؟

يحتوي الإصدار الجديد على العديد من الفئات و الواجهات كما يُقدم أيضا بعض الــ abstract classes التي تقوم بعمل implementation لبعض الــ interfaces.

الواجهات التي يمكن استخدامها مع كائنات للتعامل مع الــ collections هي :

  • Collection : معظم الفئات التي تتعامل مع الــ collections قامت بعمل implement لهذه الواجهة.
  • Map : هذه الواجهة تحتوي على الدوال أو الــ methods الخاصة بالكائنات التي تتعامل مع الــ associative array على شكل key/value.
  • Set : خاصة بالكائنات التي لا تقبل التكرار داخل المجموعة.
  • List : الكائنات التي تقبل التكرار داخل المجموعة و تدعم الوصول إلى العناصر بشكل مباشر.
  • SortedSet : هذه الواجهة ترث الواجهة Set و تسمح بترتيب العناصر.
  • SortedMap : ترث الواجهة Map و تسمح بترتيب العناصر.

بعض الدوال (اعذرني أخي القارئ فأنا أفضل كلمة الدوال على مصطلح الطرق) الموجودة في الواجهات السابقة, اختيارية بمعنى أن تعريفها اجباري لكن إذا كانت العملية غير مدعومة فيمكن للدالة أن تُصدر exception تُوضح نوع خطأ الاستخدام. الهدف من شيء كهذا هو تقليل عدد الواجهات و تلبية أكبر عدد ممكن من الحاجات المطلوبة.

 أيضا, الــ framework يوفر العديد من الكائنات التي تدعم الواجهات السابقة و يمكن استخدامها بشكل مباشر:

  • HashSet : عبارة عن Hash table أو جدول هاش يدعم الواجهة Set.
  •  TreeSet : شجرة (tree) قامت بعمل implement للواجهة SortedSet.
  • ArrayList : مصفوفة ديناميكية قامت بعمل implement للواجهة List.
  • LinkedList : قائمة متصلة مزدوجة قامت بعمل implement للواجهة List.
  • HashMap : جدول هاش (Hash table) قام بعمل implement للواجهة Map.
  • TreeMap : شجرة (tree) قامت بعمل implement للواجهة SortedMap.

بالنسبة للواجهات التي تُمكن من المرور على عناصر الــ collections و ترتيبها فهي :

  • Iterator : للتجول بين مختلف عناصر الــ collection.
  • ListIterator : تسمح بالمرور على عناصر List في الاتجاهين كما تسمح بتغيير عناصر المجموعة.
  • Comparable : لتعريف نوع الترتيب الطبيعي لكائن معين.
  • Comparator : لتعريف ترتيب كيفي (لا على التعيين).

هذا بالإضافة إلى فئتين كانتا توجدان في النسخة السابقة و أجريت عليهما بعض التعديلات لدعم بعض الواجهات الجديدة:

  • Vector : مصفوفة ديناميكية قامت بعمل implement للواجهة الجديدة List
  • HashTable : Hash table قام بعمل implement للواجهة الجديدة Map

 

المخطط الجديد لــ Collections FrameWork

1 -  Collection Hierarchy
 

post-219439-0-05164600-1381499633_thumb.
 

1.1 -  الواجهة Collection

public interface Collection {    // Basic Operations    int size();    boolean isEmpty();    boolean contains(Object element);    boolean add(Object element); // Optional    boolean remove(Object element); // Optional    Iterator iterator();    int hashCode();    boolean equals(Object element);    // Bulk Operations    boolean containsAll(Collection c);    boolean addAll(Collection c); // Optional    boolean removeAll(Collection c); // Optional    boolean retainAll(Collection c); // Optional    void clear(); // Optional    // Array Operations    Object[] toArray();    Object[] toArray(Object a[]);}

كما قلنا سابقا, أغلب الواجهات الجديدة تحتوي على دوال اختيارية, هذه الخاصية تسمح لك بالتعامل مع هياكل البيانات الخاصة دون أن تحتاج إلى تعريف دوال جديدة. الدوال الاختيارية تُستعمل عند الحاجة فقط. على سبيل المثال إذا كان لدينا Set غير قابلة للتعديل يمكننا إعادة تعريف الدالة add بحيث نمنع التعديل أو الإضافة. تختلف الدوال باختلاف مهامها فهناك بعض الدوال ينفذ العملية بشكل فردي (أي على كائن واحد) و هناك أخرى تنفذها بشكل جماعي أي على مجموعة كائنات في آن واحد.

  • الدالتان add و remove تسمحان بإضافة أو حذف عنصر معين بينما تقوم addAll أو removeAll بحذف أو إضافة مجموعة كائنات بشكل جماعي.
  • الدالة contains تتحقق من وجود عنصر داخل المجموعة. إذا أردنا التحقق من وجود مجموعة عناصر في آن واحد نستخدام containsAll.
  • size, isEmpty و clear يمكنهم (على التوالي) معرفة حجم الــ collection, التحقق من أن الــ collection فارغة و الأخيرة تُمكن من حذف محتوى الــ collection.
  • retainAll تُستعمل لإيجاد تقاطع الــ collections, فمثلا إذا كانت  A={1,2,5,8} و B={3,8} فإن A ستصبح {8}.
  • equals تتحقق من تطابق الــ collections.
  • hashCode تُعيد كود الــ hash code الخاص بالــ collection.
  • toArray تُعيد عناصر الــ collection على شكل مصفوفة.
  • toArray(Object a[])l تسمح بتحديد نوع المصفوفة المعادة.

 

للمرور على عناصر collection نستخدم الواجهة Iterator :

public interface Iterator {    boolean hasNext();    Object next();    void remove(); // Optional}
  • hasNext : تتحقق من وجود عنصر قادم.
  • next : تُشير إلى العنصر القادم.
  • remove : تحذف العنصر الحالي.

 

ملاحظة:

الدالة next تُصدر NoSuchElementException عند استدعائها بعد انتهاء عناصر الــ collection. لتفادي هذه الـــ Exception يكفي تطبيق hasNext على الكائن المُعاد من طرف الدالة next, هكذا :

Iterator iter = objetCollection.iterator();while (iter. hasNext()){    Object o = iter.next() ;    System.out.println ("Objet : " + o);}

1.2 -  الواجهة Set

هذه الواجهة عبارة عن Collection غير مرتبة, لا تسمح بتكرار العناصر. يوجد نوعان من الــ implementation, الاختيار بينهما يكون حسب ضرورة ترتيب العناصر:

  • HashSet : العناصر غير مرتبة, عملية الإضافة سريعة جدا.
  • TreeSet : العناصر مرتبة, عملية الإضافة تكون ابطأ.

مالفرق بين الاثنين ؟

  • HashSet : تستخدم تقنية الــ hashing لتحديد تكرار العنصر و تعتمد على الدالة hashCode في الترتيب.
  • TreeSet : تستخدم binary tree لترتيب العناصر و تعتمد على الدالة compareTo في الترتيب.

 

ملاحظات:

  1. نوع الترتيب المُستخدم يتم تحديده من طرف الكائنات المضافة إلى TreeSet من خلال عمل implementation للواجهة Comparable إذا أردنا الترتيب حسب natural sort order أو يمكننا تمرير كائن من Comparator لدالة بناء TreeSet إذا أردنا تحديد الــ sort order.
  2. للتأكد من وجود عنصر معين داخل Set استخدم الدالة equals. مع العلم طبعا بأنه يجب إعادة تعريف equals حتى تستطيع التأكد من وحدانية أو عدم تكرار الكائن المضاف.

انظر المثال:

import java.util.HashSet;import java.util.Set;import java.util.TreeSet;public class SetExample {    public static void main(String args[]) {        Set set = new HashSet(); // Une table de Hachage        set.add("Bernadine");        set.add("Elizabeth");        set.add("Gene");        set.add("Elizabeth");        set.add("Clara");        System.out.println(set);        Set SetTrie = new TreeSet(set); // Un Set trié        System.out.println(SetTrie);    }}

 

1.3 -  الواجهة List

List عبارة عن collection مرتبة تسمح بتكرار العناصر, تحتوي هذه الواجهة على دوال تُمكن من إضافة أو حذف عنصر في أي مكان من المجموعة. كما تسمح بالتعامل مع القوائم المتداخلة, في أغلب الحالات نستخدم ArrayList إلا إذا كانت هناك عمليات إضافة في المنتصف, عندها يُستحسن استخدام LinkedList لتفادي عمليات الإزاحة المتكررة.

public interface List extends Collection {    // Positional Access    Object get(int index);    Object set(int index, Object element); // Optional    void add(int index, Object element); // Optional    Object remove(int index); // Optional    boolean addAll(int index, Collection c); // Optional    // Search    int indexOf(Object o);    int lastIndexOf(Object o);    // Iteration    ListIterator listIterator();    ListIterator listIterator(int index);    // Range-view    List subList(int fromIndex, int toIndex);}

دوال هذه الواجهة تسمح بإجراء بعض العمليات على عنصر يتم تحديد موقعه أو على مجموعة عناصر ابتداء من index معين.

  • الدالة get تُعيد العنصر الموجود في الموقع index.
  • set تُغير العنصر الموجود في الموقع index.
  • indexOf (على التوالي lastIndexOf) تبحث عن وجود العنصر في المجموعة و تُعيد رقم موقعه (على التوالي رقم آخر ظهور له) في المجموعة.
  • subList تسمح بإنشاء قائمة داخل أخرى.

للمرور على عناصر List تم إنشاء Iterator خاص :

public interface ListIterator extends Iterator {    boolean hasNext();    Object next();    boolean hasPrevious();    Object previous();    int nextIndex();    int previousIndex();    void remove(); // Optional    void set(Object o); // Optional    void add(Object o); // Optional}

حيث يسمح بالمرور على عناصر List في الاتجاهين بالإضافة إلى إمكانية تغيير عنصر معين أو إضافة آخر جديد :

List list = ...;ListIterator iterator = list.listIterator(list.size());while (iterator.hasPrevious()) {       Object element = iterator.previous();       // traitement d'un élément}

nextIndex تُعيد رقم موقع العنصر الحالي و بقية الدوال رأيناها سابقاً.

 

1.4 -  الكلاس ArrayList

هذا الكلاس يرث الكلاس AbstractList مما يعني أنه قام بعمل implement للواجهة List يُمثل مصفوفة ديناميكية من الكائنات. طريقة عمله مطابقة تماما لتلك الخاصة بــ Vector, الفرق بينهما هو أن هذا الأخير يدعم الــ multi-thread (جميع الدوال synchronized). اذا اردنا استخدام thread واحد فالــ synchronization تكون مكلفة و عديمة الفائدة لذا من الأفضل استخدام ArrayList في مثل هذه الحالات.

اضافة إلى الدوال السابقة توجد:

  • ensureCapacity : تسمح بزيادة حجم المصفوفة لضمان تخزين العناصر الجديدة.
  • trimToSize : تسوية سعة المصفوفة على حجمها الحالي.

مثال أول:

import java.util.ArrayList;public class TestArrayList01 {    public static void main(String[] args) {        ArrayList c = new ArrayList();        /*         * ajout de dix element de type Integer         */        for (int i = 0; i < 10; i++) {            c.add(new Integer(i));        }        /*         * affiche des element par accès direct         */        for (int i = 0; i < c.size(); i++) {            System.out.print(c.get(i) + " ");//1        }        System.out.println();        /*         * suppression d'element aux positions indiquées         */        c.remove(2);        c.remove(4);        c.remove(6);        //c.remove(8) ;        /*         * affiche de la liste         */        System.out.print(c.toString());//2        /*         * ajout de dix element de type Integer         */        c.add(2, "100");        c.add(2, "200");        c.add(5, "300");        /*         * réaffiche de la liste         */        System.out.print(c.toString());//3    }}

مُخرجات الكود ستكون بهذا الشكل:

post-219439-0-36309900-1381499675.png

بعد حذف 2, 5 و 8 سيكون طول المصفوفة يساوي 7 و بالتالي عندما نحاول حذف العنصر الثامن سيتم رفع استثناء من نوع IndexOutOfBoundsException.

 

مثال ثاني:

import java.util.ArrayList;import java.util.ListIterator;public class TestArrayList02 {    public static void main(String[] args) {        ArrayList c = new ArrayList();        /*         * ajout de dix element de type Integer         */        for (int i = 0; i < 10; i++) {            c.add(new Integer(i));        }        /*         * affiche toute la liste         */        System.out.println(c.toString());        /*         * iterateur sur le tableau         */        ListIterator iter = c.listIterator();        while (iter.hasNext()) {            Integer k = (Integer) iter.next();            int i = k.intValue();            if (i % 2 == 0) {                iter.set(new Integer(500));            }        }        /*         * affiche toute la liste         */        System.out.print(c.toString());    }}

مُخرجات الكود ستكون على النحول التالي:

post-219439-0-61106300-1381499690.png

 

 

 

2 - Map Hierarchy

 

post-219439-0-48289600-1381499656_thumb.

 

2.1 – ماهو الــ Map ؟

الــ Map عبارة عن مجموعة أزواج, كل زوج يحتوي على مفتاح و قيمة key/value مع العلم أن المفتاح لا يمكن تكراره. (في الحقيقة يمكننا اسناد عدة قيم لمفتاح واحد, في هذه الحالة سنكون أمام multimap). الهدف الأساسي من الــ Map هو الوصول بسرعة إلى المفتاح للحصول على القيمة المقابلة له.

public interface Map {    // Basic Operations    Object put(Object key, Object value);    Object get(Object key);    Object remove(Object key);    boolean containsKey(Object key);    boolean containsValue(Object value);    int size();    boolean isEmpty();    // Bulk Operations    void putAll(Map t);    void clear();    // Collection Views    public Set keySet();    public Collection values();    public Set entrySet();    // Interface for entrySet elements    public interface Entry {        Object getKey();        Object getValue();        Object setValue(Object value);    }}

2.2 - عرض سريع لمختلف الدوال الموجودة داخل الواجهة Map

  • الدالة clear تسمح بحذف جميع عناصر الــ Map.
  • containsKey تُحدد ما إذا كان المفتاح key موجود أو لا.
  • containsValue تُحدد ما إذا كانت القيمة value موجودة داخل الــ Map.
  • entrySet تُعيد Set تحتوي على جميع قيم الــ Map.
  • get تُعيد القيمة المرافقة للمفتاح key.
  • isEmpty تتحق من فراغ الــ Map.
  • keySet تُعيد Set تحتوي على كافة مفاتيح الــ Map.
  • put تضيف المفتاح key و القيمة المرافقة له value داخل الــ Map.
  • putAll تضيف كافة المفاتيح و القيم داخل الــ Map.
  • values تُعيد قيم الــ Map على شكل collection.
  • remove يحذف العنصر المقابل للمفتاح key.
  • size تُعيد عدد عناصر الــ Map.

 

2.3 - ماهي انواع الــ implementation الموجودة ؟

يوجد نوعان من الــ Map :

  • HashMap, Hashtable : يستخدمان جدول هاش.
  • TreeMap : تستخدم شجرة ثنائية.

 

ملاحظات :

  1. الفئة HashMap ليست synchronized. للتعامل مع الــ concurrent access, قم بتغليف الكائن داخل كائن من Map باستخدام الدالة synchronizedMap الموجودة في الواجهة Collection.
  2. إذا كان المفتاح المُمرر إلى put موجود مسبقا سيتم استبدال القيمة القديمة بالجديدة. put تُعيد القيمة القديمة إذا كان المفتاح موجود مسبقا و إلا فستعيد null.
  3. TreeMap قامت بعمل implement لــ SortedMap, ترتيب العناصر يتم تحديده عن طريق كائن من نوع Comparable.

 

2.4 - ماهي الــ view ؟

  • تسمح الــ view بالمرور على عناصر الـــ Map.
  • نظريا, الفئتان HashMap و TreeMap لا يملكان أي Iterator.
  • الدالة entrySet تسمح برؤية Map كمجموعة ازواج.
  • الزوج عبارة عن كائن من نوع الواجهة Map.entry حيث يجمع كائنين في آن واحد.
  • الدالتان getKey و getValue الموجودتان في Map.entry تسمحان باستخراج مفتاح و قيمة زوج معين.

 

انظر المثال:

import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public class TestHashMapView {    public static void main(String[] args) {        HashMap table = new HashMap();        /*         * ajout d'éléments dans la table         */        table.put(new Integer(1), "Livre Java");        table.put(new Integer(2), "Livre Oracle");        table.put(new Integer(3), "Livre C++");        table.put(new Integer(4), "Livre Reseaux");        Set entrees = table.entrySet();// entrees est un ensemble de paires        Iterator iter = entrees.iterator(); // un iterateur sur les paires        while (iter.hasNext()) {            Map.Entry entree = (Map.Entry) iter.next(); // on est sur la paire courante            Object cle = entree.getKey();            Object valeur = entree.getValue();            if (cle != null && cle.toString().equals(new Integer(2).toString())) {                table.put(new Integer(2), "Revue Technical Report");            }        }        System.out.println(table.get(new Integer(2)));    }}

بالإضافة إلى الــ view السابقة توجد أخرى, للحصول على كافة المفاتيح نستخدم الدالة keySet :

import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public class ExampleKeySet {    public static void main(String[] args) {        HashMap map = new HashMap();        map.put("1", "stock");        map.put("2", "livres");        map.put("3", "sucre");        map.put("4", "huile");        //ensemble des cles        Set cles = map.entrySet();        System.out.println("Aff1 " + cles);        Iterator iter = cles.iterator();        while (iter.hasNext()) {            Map.Entry entree = (Map.Entry) iter.next();            String s = (String) entree.getValue();            if (s.equals("huile")) {                entree.setValue("riz");            }            System.out.println("Element courant " + s);        }        System.out.println("Affiche " + cles);    }}

للحصول على كافة القيم نستخدم الدالة values :

import java.util.Collection;import java.util.HashMap;import java.util.Iterator;public class ExampleValues {    public static void main(String[] args) {        HashMap map = new HashMap();        map.put("1", "stock");        map.put("2", "livres");        map.put("3", "sucre");        map.put("4", "huile");        //collection des valeurs        Collection valeurs = map.values();        System.out.println("Affichage 1 " + valeurs);        Iterator iter = valeurs.iterator();        while (iter.hasNext()) {            Object o = iter.next();            if (o.toString().equals("huile")) {                iter.remove();            }            System.out.println("Element courant " + o);        }        System.out.println("Affichage 2 " + valeurs);    }}

ملاحظات:

  1. أي تغيير يحدث في الـــ Map سيتم اجراؤه أيضا على الــ view المقابلة.
  2. حذف الزوج أو العنصر الحالي (باستخدام remove) في الــ view يقتضي أيضا حذف العنصر المقابل له في الــ Map.
  3. لا يُسمح بإضافة عناصر مباشر من داخل الــ view.

 

مثال جامع :

ليكن لدينا الكلاس التالي:

public class Produit {    private String idProduit, libelle, quantite;    //نفترض أن الكلاس يحتوي على دالة بناء بالإضافة إلى	//getters and setters}

نُعطي الكلاس EnregistrementProduit المعرف كما يلي: 

import java.util.ArrayList;import java.util.HashMap;public class EnregistrementProduit {    private static HashMap<String, Produit> prod = new HashMap<String, Produit>();    public static HashMap<String, Produit> enreg(Produit e) {        //أكمل الكود    }    public static ArrayList<Produit> chercherProduits(HashMap<String, Produit> prod) {        //أكمل الكود    }}
  1. قم بتعريف الدالة enreg بحيث تقوم بتخزين الــ Produit الذي تم تمريره كوسيط داخل جدول الهاش prod. نعتبر أن idProduit هو المفتاح.
  2. قم أيضا بتعريف الدالة chercherProduits بحيث تقوم بإعادة مجموعة الــ Products ذو quantite أقل من 15000.

 

الحل:

import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.Iterator;class Produit {    private String idProduit, libelle, quantite;    public Produit(String id, String libel, String qnt) {        idProduit = id;        libelle = libel;        quantite = qnt;    }    public String getId() {        return idProduit;    }    public String getQnt() {        return quantite;    }}public class EnregistrementProduit {    private static HashMap<String, Produit> prod = new HashMap<String, Produit>();    public static HashMap<String, Produit> enreg(Produit p) {        prod.put(p.getId(), p);        return prod;    }    public static ArrayList<Produit> chercherProduits(HashMap<String, Produit> prod) {        Collection c = prod.values();        ArrayList<Produit> array = new ArrayList<Produit>();        Iterator<Produit> iter = c.iterator();        while (iter.hasNext()) {            Produit p = iter.next();            if (Integer.parseInt(p.getQnt()) > 15000) {                array.add(p);            }        }        return array;    }}

ترتيب الــ Collections

يتم تحديد نوع الترتيب المُستخدم في الــ Collections باستخدام احدى الواجهتين :

Comparable Or Comparator

 

1 - الواجهة java.lang.Comparable

جميع الكائنات التي تستخدم natural sort order يجب أن تقوم بعمل implement لهذه الواجهة:

interface Comparable {    int compareTo(Object o);}

بعض الفئات مثل String, File, Date, Integer, Float الخ, عملت implement لهذه الواجهة.

Comparable تحتوي على دالة واحدة فقط هي compareTo التي تعيد احدى القيم التالية:

  • قيمة اكبر من الصفر, إذا كان الكائن الحالي (current object) اكبر من الكائن الذي تم تمريره.
  • اصغر من الصفر, إذا كان الكائن الحالي (current object) اصغر من الكائن الذي تم تمريره.
  • صفر, اذا تطابق الكائنان.

 

مثال:

قم بكتابة كلاس باسم TestComparable يقوم بعمل implement للواجهة Comparable و يسمح بترتيب عناصر موجودة في HashMap على النحو التالي:

  1. الدالة void tri (Object [ ]o)l تسمح بترتيب جدول من Object.
  2. الدالة ArrayList triHashMap (HashMap map)l تستقبل HashMap كوسيط و تُعيد ArrayList يحتوي على قيم الــ HashMap بشكل مرتب.
  3. مع العلم أنه يجب اعادة تعريف الدالة CompareTo حتى توافق المتطلبات.

 

الحل:

import java.util.*;public class TestComparable implements Comparable {    public int compareTo(Object o) {        if (o instanceof String) {            String d = (String) o;            return this.compareTo(d);        } else {            new ClassCastException();            return -1;        }    }    ArrayList triHashMap(HashMap map) {        int i = 0;        ArrayList liste = new ArrayList();        Set c = map.entrySet();        Object tab[] = new Object[c.size()];        Iterator it = c.iterator();        Map.Entry mp = null;        while (it.hasNext()) {            mp = (Map.Entry) it.next();            tab[ i] = mp.getValue();            i++;        }        tri(tab);        for (int ii = 0; ii < tab.length; ii++) {            liste.add(tab[ ii]);        }        return liste;    }    public void tri(Object[] tab) {        for (int i = 0; i < tab.length - 1; i++) {            if (tab[i].toString().compareTo(tab[i + 1].toString()) > 0) {                String temp = tab[i].toString();                tab[i] = tab[i + 1];                tab[i + 1] = temp;                tri(tab);            }        }    }    public static void main(String[] args) {        TestComparable tt = new TestComparable();        HashMap map = new HashMap();        map.put("1", "stock");        map.put("2", "livres");        map.put("3", "sucre");        map.put("4", "huile");        map.put("5", "riz");        map.put("6", "or");        Collection val = map.values();        System.out.println("aff 1 " + val);        ArrayList h = tt.triHashMap(map);        System.out.println("aff 2 " + h.toString());    }}

2 - الواجهة java.util.Comparator

هذه الواجهة تُمثل sort order كيفي, أي أن المبرمج هو من يحدده. حيث تسمح بترتيب الكائنات التي لم تقم بعمل implement للواجهة Comparable أو تعريف sort order مختلف عن الذي تقدمة الواجهة السابقة.

interface Comparator {	int compare(Object o, Object v);	boolean equals(Object object);}

الدالة compare تعيد:

  • قيمة اكبر من الصفر, إذا كان o اكبر من v.
  • اصغر من الصفر, إذا كان o اصغر من v.
  • صفر, اذا كان o و v متطابقان.

قم بعمل المثال السابق باستخدام الواجهة Comparator.

 

الـــ Algorithmes :

مجموعة الـــ algorithmes التي تُمكن من التعامل مع الـــ collections موجودة في الكلاس Collections (انتبه جيداً, الكلاس Collections يختلف تماما عن الواجهة Collection). تم تعريف جميع تلك الدوال على أنها static.

نبدأ بالـــ algorithmes الخاصة بــ List :

الترتيب:

  • sort(List list)l : ترتيب List
  • sort(List list,Comparator comp)l : ترتيب List باستخدام comparator

الــ Merge :

shuffle(List liste)l  : يخلط العناصر بطريقة عشوائية

البحث:

  • binarySearch(List list, Object element)l : البحث عن عنصر باستخدام خوارزمية البحث الثنائي
  • binarySearch(List list, Object element, Comparator comp)l : البحث عن عنصر باستخدام خوارزمية البحث الثنائي اعتمادا على comparator.

بعض العمليات الأخرى:

  • reverse(List liste)l : قلب عناصر List
  • fill (List liste, Object element)l : تقوم بعمل initialization لـ List باستخدام element
  • copy(List dest, List src)l  : نسخ src داخل dest

أيضا هناك خوارزميات أخرى يمكن تطبيقها على جميع الــ collections مثل :

  • min (Collection)l : للبحث عن أصغر عنصر
  • max (Collection)l : للبحث عن أكبر عنصر

 

ملاحظات:

  • لاستخدام الدالة sort(List)l يجب أن تقوم كافة عناصر الـــ List بعمل implement للواجهة Comparable وإلا سيتم رفع استثناء من نوع ClassCastException.
  • الكلاس Collections يحتوي على دوال خاصة تسمح بالحصول على نسخة multi-thread و Unmodifiable لمعظم الــ interfaces  الموجودة مثل Collection, List, Map,Set, SortedMap, SortedSet :
  1. synchronizedXXX ( XXX)l : للحصول على نسخة multi-thread من الكائنات التي قامت بعمل impelement للواجهة XXX
  2. unmodifiableXXX (XXX)l : للحصول على نسخة Unmodifiable من الكائنات التي قامت بعمل impelement للواجهة XXX

 

مثال على خاصية الــ Unmodifiable :

public class TestUnmodifiable {    public static void main(String args[]) {        java.util.List list = new java.util.ArrayList();        list.add(new Integer(100));        list.add(new Integer(200));        list.add(new Integer(300));        java.util.List listun;        listun = java.util.Collections.unmodifiableList(list);        list.add(new Integer(400));        listun.add(new Integer(400)); // tentative illégale de modification de la liste l        System.out.println(list.toString());    }}

محاولة تغيير Unmodifiable List تقتضي اصدار استثناء من نوع java.lang.UnsupportedOperationException.

 

مثال على خاصية الــ synchronization :

import java.util.Collections;import java.util.Iterator;import java.util.LinkedList;import java.util.List;public class Testsynchronized {    public static void main(String[] args) {        List list = new LinkedList();        list.add(new Integer(800));        list.add(new String("livres"));        list.add("revue");        list.add("articles");        list.add(new Integer(122));        list.add(new Object());        list = Collections.synchronizedList(list);        synchronized (list) {            Iterator iter = list.iterator();            while (iter.hasNext()) {                if (iter.next() instanceof Integer) {                    iter.remove();                }                list.add(null); // illegal            }        }        synchronized (list) {            Iterator it = list.iterator();            while (it.hasNext()) {                if (it.next() instanceof String) {                    it.remove();                }            }        }        System.out.println(list.toString());    }}

استدعاء الدالة synchronizedXXX يُعيد كائن يدعم الــ synchronization لبعض العمليات مثل الاضافة و الحذف. أما بالنسبة لعملية المرور على العناصر باستخدام Iterator فيجب وضع الكود المخصص داخل synchronized bloc. يجب أيضا وضع استدعاء الدالة داخل الجزء المخصص.

 

محاولة اضافة عنصر جديد أثناء المرور على عناصر المجموعة, تقتضي اصدار استثناء من نوع java.util.ConcurrentModificationException.

 

 

أرجو أن أكون قد وُفقت في الشرح و أرجو لكم الاستفادة أيضا و لا تنسوني من صالح دعائكم.

 

 

مراجع:

Développons en Java - Jean Michel DOUDOUX

Notes du cours du mon Prof Joseph NDONG

Cours Java - Bernard Caylux

تم تعديل بواسطه [email protected]
تنسيق الكتابة.
4

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

3 إجابة على هذا السؤال .

  • 0

السلام عليكم ورحمة الله وبركاته
بارك الله فيك أخي ٍ[email protected] مقال رائع ومتكامل ... أراه مرجعاً عربياً ممتازاً للمجموعات Collections
أحب أن أضيف بعض التعقيبات من فضلك ..

يمكن للــ collections تخزين العناصر أيا كان نوعها بشرط أن تكون كائنات.
 يمكن مثلا أن تكون String, Integer, Float, Manager أو أي كائن آخر ..
 لكن مع ذلك من الخطر جدا استخدام collection تحتوي على أنواع مختلفة من الكائنات لأنك ستحتاج دوما إلى استخدام الــ casting أو instanceof من أجل تحديد صنف  الكائن.
 بالتالي يُستحسن دائما تخزين كائنات من نفس النوع في الــ collection الواحدة.

توفّر جافا امكانية التعامل مع المجموعات كقوالب templates وبذلك نتجنب تماماً خلط الانواع المتعددة في مجموعة واحدة ..
مثال :
خلط الأنواع داخل القائمة :
ArrayList c = new ArrayList();                for (int i = 0; i < 10; i++) {                c.add(new Integer(i));                c.add(new Character((char) ('a'+i )));                c.add(new Color(i));        }
لمنع خلط الأنواع تماماً نقوم بتعريف القائمة كقالب ..
ArrayList <Integer>c = new ArrayList<Integer>();                for (int i = 0; i < 10; i++) {                c.add(new Integer(i));                c.add(new Character((char) ('a'+i )));                c.add(new Color(i));        }
سيولد الكود أخطاءً بخصوص الأنواع الأخرى ... وهذا ما نريده بالتحديد ..
___________________________________________________________________________
بالنسبة للمرور على جميع عناصر المجموعة .. بالإضافة إلى كائنات المرور iterator تتيح جافا حلقة for المحسنة (بعد الاصدار 1.5 )
واستخدامها سهل جداً إلى درجة كبيرة ... ولكنها تسمح فقط بالتعامل مع الكائن بشكل مستقل بغض النظر عن المجموعة
مثال باستخدام كائنات المرور ..
ArrayList c = new ArrayList();        for (int i = 0; i < 10; i++) {            c.add(new Integer(i));        }        ListIterator iter = c.listIterator();        while (iter.hasNext()) {            Integer k = (Integer) iter.next();            int i = k.intValue();            if (i % 2 == 0) {                iter.set(new Integer(500));            }        }        System.out.println(c.toString());
في المثال السابق قمنا بإعادة اسناد عنصر في المجموعة وهذا غير متاح ضمن الحلقة المحسنة
ولكن يمكن استخدام الكائن بأي شكل كما في المثال التالي حيث سنطبع عناصر المجموعة
        ArrayList c = new ArrayList();        for (int i = 0; i < 10; i++) {            c.add(new Integer(i));        }       for(Object x:c)        {            System.out.println((Integer)x);        }
كما لا يمكن استعمالها مع Map

________________________________________

أخيرا لدي سؤال ... بالنسبة لخاصية التزامن .. لم أفهم أهميتها من المثال .. هل يمكنك شرحها قليلاً بعد ؟

 

وفقك الله وسدد خطاك ..

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0
لمنع خلط الأنواع تماماً نقوم بتعريف القائمة كقالب .

 

.

نعم موضوع الـ Generic Collections مهم جدا, كنت أود الكتابة عنه في هذه المقالة لكن لحسن الحظ أنني لم أكتب عنه .. يبدو أنه لا أحد يهتم :)

 

 

بالنسبة لخاصية التزامن .. لم أفهم أهميتها من المثال .. هل يمكنك شرحها قليلاً بعد ؟

 

نستخدم الــ synchronization إذا كانت لدينا collection يتعامل معها أكثر من thread من أجل الحفاظ على الــ Data Consistency أي أن كل thread يقوم بعمله دون أن يؤثر على البقية.

تم تعديل بواسطه [email protected]
2

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

مقالة مهمة ومفيدة ومثيرة

شكرا اخي لمجهودك جميل جدا وفقك الله ونتمنى المزيد

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



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

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .