مثال على التعاميم في Java – الأسلوب العام، الصف، الواجهة

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

سننظر في المواضيع التالية للجينريكس في جافا.

  1. الجينريكس في جافا

  2. فئة جافا الجينريكية

  3. واجهة جافا الجينريكية

  4. نوع جافا الجينريكي

  5. طريقة جافا الجينريكية

  6. معلمات النوع المقيدة للجينريكس في جافا

  7. تعميمات جافا والوراثة

  8. فئات جافا العامة وتحت الفئة

  9. البطاقات البريدية الجافا

  10. البطاقات البريدية العلوية المحددة لجافا

  11. البطاقات البريدية العلوية غير المحددة لجافا

  12. البطاقات البريدية السفلية المحددة لجافا

  13. الفئات الفرعية باستخدام بطاقة الجافا

  14. مسح نوع التعميمات الجافا

  15. أسئلة شائعة حول التعميمات

1. التعميمات في جافا

تمت إضافة التعميمات في جافا 5 لتوفير فحص النوع في وقت الترجمة وإزالة خطر ClassCastException الذي كان شائعًا أثناء العمل مع فئات المجموعات. تم إعادة كتابة الإطار الكامل للمجموعات لاستخدام التعميمات لضمان سلامة النوع. دعونا نرى كيف تساعدنا التعميمات في استخدام فئات المجموعات بأمان.

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); //OK

for(Object obj : list){
	// تحويل النوع الذي يؤدي إلى ClassCastException أثناء التشغيل
    String str=(String) obj; 
}

الكود أعلاه يتم تجميعه بشكل جيد ولكنه يُلقي استثناء ClassCastException أثناء التشغيل لأننا نحاول تحويل Object في القائمة إلى String في حين أن أحد العناصر من نوع Integer. بعد جافا 5، نستخدم فئات المجموعات مثل ما يلي.

List list1 = new ArrayList(); // java 7 ? List list1 = new ArrayList<>(); 
list1.add("abc");
//list1.add(new Integer(5)); // خطأ في الترجمة

for(String str : list1){
     // لا يوجد حاجة لتحويل النوع، يمنع ClassCastException
}

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

2. فئة جافا التعميمية

يمكننا تعريف الفئات الخاصة بنا بأنواع الجنريك. النوع الجنريك هو فئة أو واجهة تم تعيينها بشكل معلم على أنواع. نستخدم الزاويتين (<>) لتحديد المعلمة النوعية. لفهم الفائدة، دعونا نفترض أن لدينا فئة بسيطة كما يلي:

package com.journaldev.generics;

public class GenericsTypeOld {

	private Object t;

	public Object get() {
		return t;
	}

	public void set(Object t) {
		this.t = t;
	}

        public static void main(String args[]){
		GenericsTypeOld type = new GenericsTypeOld();
		type.set("Pankaj"); 
		String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
	}
}

لاحظ أنه أثناء استخدام هذه الفئة، يتعين علينا استخدام تحويل النوع ويمكن أن ينتج ClassCastException في وقت التشغيل. الآن سنستخدم فئة جافا الجنرية لإعادة كتابة نفس الفئة كما هو موضح أدناه.

package com.journaldev.generics;

public class GenericsType<T> {

	private T t;
	
	public T get(){
		return this.t;
	}
	
	public void set(T t1){
		this.t=t1;
	}
	
	public static void main(String args[]){
		GenericsType<String> type = new GenericsType<>();
		type.set("Pankaj"); //valid
		
		GenericsType type1 = new GenericsType(); //raw type
		type1.set("Pankaj"); //valid
		type1.set(10); //valid and autoboxing support
	}
}

لاحظ استخدام فئة GenericsType في الأسلوب الرئيسي. لا داعي لعمليات تحويل النوع ويمكننا إزالة ClassCastException في وقت التشغيل. إذا لم نقدم النوع في وقت الإنشاء، سيقوم المترجم بإنتاج تحذير بأن “GenericsType هو نوع غير محدد. يجب تعيين مراجع إلى النوع الجنريك GenericsType“. عندما لا نقدم النوع، يصبح النوع Object وبالتالي يسمح بكل من السلاسل والأعداد الصحيحة. ولكن، يجب علينا دائمًا محاولة تجنب هذا لأنه سنحتاج إلى استخدام تحويل النوع أثناء العمل على النوع الغير محدد الذي قد ينتج أخطاء في وقت التشغيل.

تلميح: يمكننا استخدام @SuppressWarnings("rawtypes") تعليقًا لقمع تحذير المترجم، تحقق من دليل الأوامر في جافا عن التعليقات.

لاحظ أيضًا أنه يدعم تحويط الجافا.

3. واجهة جافا العامة

واجهة Comparable هي مثال رائع على التعميمات في الواجهات ويتم كتابتها على النحو التالي:

package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}

بنفس الطريقة، يمكننا إنشاء واجهات عامة في جافا. يمكننا أيضًا إضافة معلمات من نوع متعدد كما في واجهة Map. مرة أخرى يمكننا توفير قيم معلمة لنوع معلم أيضًا، على سبيل المثال new HashMap<String، List<String>>(); صالح.

4. نوع جافا العام

تساعدنا تسمية النوع الجافة في جافا على فهم الكود بسهولة وإن وجود تسمية له هو واحد من أفضل الممارسات في لغة برمجة جافا. لذلك، تأتي التعميمات أيضًا مع تسمياتها الخاصة. عادةً ما تكون أسماء معلمات النوع أحرفًا كبيرة فردية لتمييزها بسهولة عن متغيرات جافا. أكثر أسماء معلمات النوع استخدامًا هي:

  • E – Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.)
  • K – Key (Used in Map)
  • N – Number
  • T – Type
  • V – Value (Used in Map)
  • S,U,V etc. – 2nd, 3rd, 4th types

5. طريقة جافا العامة

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

package com.journaldev.generics;

public class GenericsMethods {

	//Java Generic Method
	public static  boolean isEqual(GenericsType g1, GenericsType g2){
		return g1.get().equals(g2.get());
	}
	
	public static void main(String args[]){
		GenericsType g1 = new GenericsType<>();
		g1.set("Pankaj");
		
		GenericsType g2 = new GenericsType<>();
		g2.set("Pankaj");
		
		boolean isEqual = GenericsMethods.isEqual(g1, g2);
		//البيان السابق يمكن كتابته ببساطة كما يلي
		isEqual = GenericsMethods.isEqual(g1, g2);
		//هذه الميزة، المعروفة باسم الاستدلال على النوع، تتيح لك استدعاء طريقة جينيريك كطريقة عادية، دون تحديد نوع بين الزوايا.
		//سيقوم المترجم بتستنتج النوع المطلوب
	}
}

لاحظ توقيع الطريقة isEqual وكيفية استخدام الجينيريك في الطرق. كما لاحظ كيفية استخدام هذه الطرق في برنامجنا جافا. يمكننا تحديد النوع أثناء استدعاء هذه الطرق أو يمكننا استدعائها كطريقة عادية. المترجم في جافا ذكي بما فيه الكفاية لتحديد نوع المتغير المراد استخدامه، وتسمى هذه الإمكانية استدلال النوع.

6. معلمات النوع المحصور في جافا Generics Bounded Type Parameters

نفترض أننا نريد تقييد نوع الكائنات التي يمكن استخدامها في النوع المعلم بالمعاملات، على سبيل المثال في طريقة تقارن بين كائنين ونريد التأكد من أن الكائنات المقبولة هي Comparables. لتعريف معلمة نوع محدودة، قم بسرد اسم المعلمة النوعية، تليها كلمة الرئيسية extends، ثم الحد الأعلى لها، مماثلة للطريقة أدناه.

public static <T extends Comparable<T>> int compare(T t1, T t2){
		return t1.compareTo(t2);
	}

استدعاء هذه الطرق مماثل للطريقة غير المقيدة باستثناء أنه إذا حاولنا استخدام أي فئة ليست Comparable، فسيتم إلقاء خطأ في وقت الترجمة. يمكن استخدام معلمات النوع المحدودة مع الطرق بالإضافة إلى الفئات والواجهات. يدعم الجينيريكس في جافا حدودًا متعددة أيضًا، أي <T extends A & B & C>. في هذه الحالة، يمكن أن تكون A واجهة أو فئة. إذا كانت A فئة، فإن B و C يجب أن تكون واجهات. لا يمكننا أن نملك أكثر من فئة في حدود متعددة.

7. الجينيريكس في جافا والتوريث

نحن نعلم أن التوريث في جافا يسمح لنا بتعيين متغير A إلى متغير B إذا كان A فئة فرعية لـ B. لذلك قد نعتقد أن أي نوع جينيريك من A يمكن تعيينه إلى نوع جينيريك من B، لكن هذا ليس الحال. لنرى هذا ببرنامج بسيط.

package com.journaldev.generics;

public class GenericsInheritance {

	public static void main(String[] args) {
		String str = "abc";
		Object obj = new Object();
		obj=str; // works because String is-a Object, inheritance in java
		
		MyClass myClass1 = new MyClass();
		MyClass myClass2 = new MyClass();
		//myClass2=myClass1; // خطأ في الترجمة لأن MyClass ليست MyClass
		obj = myClass1; // MyClass parent is Object
	}
	
	public static class MyClass{}

}

لا يُسمح لنا بتعيين متغير MyClass إلى متغير MyClassلأنهما غير مرتبطين، في الواقع والأصل MyClass هو Object.

8. فئات الجنيريك في جافا والترميز الفرعي

يمكننا تحديد نوع جنيريك أو واجهة فرعية من خلال تمديده أو تنفيذه. يتم تحديد العلاقة بين معلمات النوع في فئة أو واجهة ومعلمات النوع في أخرى بواسطة عبارات extends وimplements. على سبيل المثال، ArrayList تنفذ List الذي يمتد من Collection، لذا ArrayList هو فرع فرعي لـ List و List هو فرع فرعي لـ Collection. يتم الحفاظ على العلاقة الفرعية طالما أننا لا نقوم بتغيير الوسيطة، ويظهر أدناه مثال على عدة معلمات نوعية.

interface MyList<E,T> extends List<E>{
}

يمكن أن تكون الأنواع الفرعية لـ List MyList، MyList وهكذا.

9. الرموز المميزة في جافا جنيريك

العلامة التعجيزية (?) هي البديل العام في التعميمات وتمثل نوعًا مجهولًا. يمكن استخدام البديل العام كنوع لمعلمة أو حقل أو متغير محلي وأحيانًا كنوع عائد. لا يمكننا استخدام البدائل العامة أثناء استدعاء طريقة تعميمية أو تثبيت فئة تعميمية. في الأقسام التالية، سنتعلم عن البدائل العامة ذات الحد العلوي، والبدائل العامة ذات الحد السفلي، وتسجيل البديل العام.

9.1) البديل العام ذو الحد العلوي للتعميم في جافا

تُستخدم البدائل العامة ذات الحد العلوي لتخفيف القيود على نوع المتغير في الطريقة. لنفترض أننا نريد كتابة طريقة ستعيد مجموع الأرقام في القائمة، لذا سيكون تنفيذنا شيئًا مثل هذا.

public static double sum(List<Number> list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}

المشكلة في التنفيذ أعلاه هي أنه لن يعمل مع قائمة الأعداد الصحيحة أو الأعداد العشرية لأننا نعلم أن List و List غير متصلة، هذا عندما يكون البديل العام ذو الحد العلوي مفيدًا. نستخدم البديل العام مع الكلمة الرئيسية extends والفئة الرئيسية أو الواجهة التي ستسمح لنا بتمرير وسيط من الحد العلوي أو أنواع الفئات الفرعية الخاصة به. يمكن تعديل التنفيذ أعلاه مثل البرنامج التالي.

package com.journaldev.generics;

import java.util.ArrayList;
import java.util.List;

public class GenericsWildcards {

	public static void main(String[] args) {
		List<Integer> ints = new ArrayList<>();
		ints.add(3); ints.add(5); ints.add(10);
		double sum = sum(ints);
		System.out.println("Sum of ints="+sum);
	}

	public static double sum(List<? extends Number> list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}
}

يتم الكتابة بطريقة مماثلة لكتابة الشيفرة الخاصة بنا من حيث الواجهة، في الطريقة أعلاه يمكننا استخدام جميع الطرق من الفئة العلوية المحددة Number. لاحظ أنه مع القائمة التي تم تحديدها من الأعلى، لا يُسمح لنا بإضافة أي كائن إلى القائمة إلا القيمة الخالية. إذا حاولنا إضافة عنصر إلى القائمة داخل طريقة sum، فلن يتم تجميع البرنامج.

9.2) الجينيريكس في جافا – علامة استفهام بلا حدود

في بعض الأحيان لدينا موقف نريد فيه أن تعمل طريقتنا الجينيريكية مع جميع الأنواع، في هذه الحالة، يمكن استخدام علامة استفهام بلا حدود. إنها نفس استخدام <? extends Object>. يمكننا تقديم List<String> أو List<Integer> أو أي نوع آخر من قائمة Object كمعامل لطريقة printData. مشابهة للقائمة المحددة من الأعلى، لا يُسمح لنا بإضافة أي شيء إلى القائمة.

public static void printData(List<?> list){
		for(Object obj : list){
			System.out.print(obj + "::");
		}
	}

9.3) الجينيريكس في جافا – علامة استفهام بحدود أسفل

نفترض أننا نريد إضافة الأعداد الصحيحة إلى قائمة من الأعداد الصحيحة في طريقة، يمكننا الاحتفاظ بنوع الوسيطة كقائمة<Integer> ولكن ستكون مقيدة بالأعداد الصحيحة بينما يمكن لقائمة<Number> وقائمة<Object> أيضًا أن تحتوي على الأعداد الصحيحة، لذا يمكننا استخدام نمط البادئة السفلية لتحقيق ذلك. نستخدم البادئة السفلية مع كلمة الرئيسية super وفئة البادئة السفلية لتحقيق ذلك. يمكننا تمرير الحد السفلي أو أي نوع فرعي للحد السفلي كوسيط، في هذه الحالة، يسمح مترجم جافا بإضافة أنواع الكائنات السفلية إلى القائمة.

public static void addIntegers(List<? super Integer> list){
		list.add(new Integer(50));
	}

10. التشبه باستخدام بادئات الجنريك

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

11. مسح الأنواع الجنريكية في جافا

تمت إضافة الجنريكات في جافا لتوفير التحقق من النوع أثناء وقت الترجمة ولا تحمل أي فائدة أثناء وقت التشغيل، لذا يستخدم مترجم جافا ميزة مسح الأنواع type erasure لإزالة جميع أكواد التحقق من الأنواع الجنريكية في البايت كود وإدراج القسمة إذا لزم الأمر. يضمن مسح الأنواع أنه لا يتم إنشاء فئات جديدة لأنواع المعلمات المقيدة؛ وبالتالي، لا تتكبد الجنريكات أي تكلفة زمنية أثناء التشغيل. على سبيل المثال، إذا كان لدينا فئة جنريكية مثل الفئة التالية؛

public class Test<T extends Comparable<T>> {

    private T data;
    private Test<T> next;

    public Test(T d, Test<T> n) {
        this.data = d;
        this.next = n;
    }

    public T getData() { return this.data; }
}

يقوم مترجم جافا بتبديل معلمة النوع المقيدة T بالواجهة المقيدة الأولى، Comparable، كما هو موضح في الكود التالي:

public class Test {

    private Comparable data;
    private Test next;

    public Node(Comparable d, Test n) {
        this.data = d;
        this.next = n;
    }

    public Comparable getData() { return data; }
}

12. الأسئلة الشائعة حول الجنريكس

12.1) لماذا نستخدم الجنريكس في جافا؟

الجنريكس توفر فحص النوع قوي في وقت الترجمة وتقلل من مخاطر ClassCastException والتحويل الصريح للكائنات.

12.2) ما هو T في الجنريكس؟

نستخدم <T> لإنشاء فئة جنريكية، واجهة جنريكية، وطريقة جنريكية. يتم استبدال T بالنوع الفعلي عند استخدامه.

12.3) كيف تعمل الجنريكس في جافا؟

الكود الجنريكي يضمن سلامة النوع. يستخدم المترجم تدريج النوع لإزالة جميع معلمات النوع في وقت الترجمة لتقليل الضغط في وقت التشغيل.

13. الجنريكات في جافا – قراءات إضافية

هذا كل شيء بالنسبة لـ الجنريكات في جافا، الجنريكات في جافا موضوع واسع حقًا ويتطلب الكثير من الوقت لفهمه واستخدامه بفعالية. هذه المقالة هنا محاولة لتوفير تفاصيل أساسية حول الجنريكات وكيف يمكننا استخدامها لتوسيع برنامجنا بأمان النوع.

Source:
https://www.digitalocean.com/community/tutorials/java-generics-example-method-class-interface