3. توقيعات الفئات والأساليب العامة
![]() |
تحتوي مكتبة RxJava على العديد من الطرق التي تقبل مثيلات واجهات عامة كمعلمات. في بعض الأحيان، تكون توقيعاتها معقدة. فيما يلي بعض الأمثلة:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
public final <R> Observable<R> flatMap(Func1<? super T,? extends Observable<? extends R>> func)
تستخدم هاتان الطريقتان نوعين عامين، T و R. ولكن ماذا يعني تعريف معلمة طريقة map، على سبيل المثال: Func1<? super T,? extends R> func ؟
يمكن العثور على معلومات حول الأنواع العامة على الرابط [https://docs.oracle.com/javase/tutorial/java/generics/]. بعض المعلومات الواردة أدناه مستمدة من هذا الرابط. تم تقديم الأنواع العامة في إصدار Java 1.5.
لنتخيل خدمة ويب تقدم معلومات من أنواع مختلفة. قد تفشل خدمة الويب أحيانًا في تقديم هذه المعلومات، وعندها يجب عليها الإبلاغ عن خطأ إلى عميلها. يمكننا بعد ذلك توحيد استجابة خدمة الويب بالشكل التالي:
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any error messages
private List<String> messages;
// the body of the reply
private T body;
...
}
- السطر 1: يتم تحديد معلمات فئة [Response] بواسطة النوع T؛
- السطر 9: النوع T هو نوع نص الاستجابة، وهو ما يتوقعه العميل فعليًا؛
- السطر 5: رمز خطأ، 0 إذا لم يكن هناك خطأ؛
- السطر 7: رسائل تشرح الخطأ، أو null في حالة عدم وجود خطأ؛
على جانب العميل، يمكننا بعد ذلك كتابة:
Response<Product> product=getDataFromWebService(...) ;
أو
Response<List<Product>>=getDataFromWebService(...) ;
يتم استبدال النوع الشكلي T بنوع فعلي، وهو هنا [Product] أو [List<Product>]. وتكون الفئة العامة [Response<T>] مفيدة هنا لأنها تسمح لنا بالتعامل مع استجابة قياسية.
لإنشاء نوع [Response] باستخدام عامل التشغيل new، نكتب:
Response<List<Product>> response=new Response<List<Product>> (...) ;
منذ إصدار Java 1.8، يمكن كتابة العبارة السابقة بشكل أبسط:
Response<List<Product>> response=new Response<> (...) ;
لم يعد هناك حاجة إلى تحديد النوع الفعلي للمعلمات العامة على الجانب الأيمن من علامة التساوي: سيتم استخدام النوع الشكلي المحدد على الجانب الأيسر من علامة التساوي بدلاً من ذلك. وهذا ما يُسمى استنتاج النوع: حيث يستطيع المُجمع تحديد النوع الفعلي للمعلمات العامة بنفسه بناءً على السياق. وتُستخدم هذه الميزة على نطاق واسع في دوال لامدا، حيث غالبًا ما يتم حذف النوع الفعلي للمعلمات العامة للطريقة.
الآن، على جانب العميل، يمكن أن يكون للطريقة [getDataFromWebService] التوقيع التالي:
<T1,T2> Response<T1> getDataFromWebService(String urlWebService, String httpMethod, T2 post)
هنا لدينا طريقة عامة معلمة بنوعين، T1 و T2:
- T1 هو نوع نص الاستجابة المتوقع؛
- T2 هو نوع القيمة المنشورة لعملية POST، و null لعملية GET؛
- urlWebService: هو عنوان URL لخدمة الويب؛
- httpMethod: هي طريقة HTTP GET أو POST التي سيتم استخدامها عند الاستعلام عن عنوان URL هذا؛
على جانب العميل، قد يكون لدينا الاستدعاء التالي:
Long id=... ;
Response<Product> product=this.<Product,Long>getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
في هذه الحالة، سيكون لدينا T1=Product و T2=Long.
مع Java 8، يمكننا استخدام استنتاج الأنواع والكتابة بطريقة أبسط:
Long id=... ;
Response<Product> product=getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
إليك استدعاء آخر ممكن:
Long[] ids=... ;
Response<List<Product>> product=getDataFromWebService('http://localhost:8080/rest/products','POST',ids) ;
في هذه الحالة، سيكون لدينا T1=List<Product> و T2=Long[].
يمكن للفئة أو الطريقة العامة أن تفرض قيودًا على معلماتها العامة. دعونا نراجع تعريف طريقة [map] في RxJava:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
تأخذ الطريقة نوعين من المعلمات، T و R. النوع [Func1] هو واجهة عامة:
![]() |
تُعرّف Func1 واجهة وظيفية، أي واجهة تحتوي على طريقة واحدة. تشكل الواجهات الوظيفية أساس تعبيرات لامدا في Java 8. هنا، تُعرّف طريقة الواجهة على النحو التالي:
إذن T هو نوع المعلمة التي يتم تمريرها إلى [call] و R هو نوع النتيجة التي يتم الحصول عليها. لنعد إلى تعريف طريقة [map]:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
- تتوقع طريقة [map] معلمة واحدة من النوع [Func1<? super T,? extends R>] وتُرجع نوع [Observable<R>]؛
- ? super T: يجب أن تكون المعلمة التي يتم تمريرها إلى طريقة [Func1.call] من النوع T أو نوعًا أعلى من T؛
- ? extends R: يجب أن يكون نوع النتيجة التي ترجعها طريقة [call] من النوع R أو نوع فرعي إذا كان R فئة، أو أن تنفذ R إذا كان R واجهة؛
لفهم القيدين أعلاه بشكل أفضل، دعونا نلقي نظرة على أمثلة من المرجع [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html]:
package tests;
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u) {
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.set(new Integer(10));
integerBox.inspect(new Long(20));
integerBox.inspect(new Double(-1.78));
//integerBox.inspect("some text"); // error: this is still String!
}
}
- السطر 3: يتم تحديد معلمات فئة [Box] بواسطة النوع T، وهو نوع الحقل الموجود في السطر 5؛
- السطر 15: تأخذ الطريقة [inspect] معلمة من النوع U. يمكن أن تحتوي الطريقة أيضًا على معلمات عامة. وفي هذه الحالة يتم إعلانها على النحو التالي:
هنا، يتم إعلان U كنوع عام للطريقة باستخدام <U> ولكن مع قيد <U extends Number>، أي أن U يجب أن يمتد إلى فئة [Number]. وبسبب هذا القيد، يبلغ المُجمع عن خطأ في السطر 25؛
- السطران 23–24: استدعاءات لطريقة [inspect] بأنواع مشتقة من [Number]؛
تم الحصول على النتائج التالية:
ملاحظة: لتشغيل الفئة في IntelliJ، اتبع الخطوات التالية [1، 2]:
![]() |
الآن، انظر المثال التالي:
public void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 3; i++) {
list.add(i);
}
}
تأخذ الطريقة [addNumbers] قائمة List<T> كمعلمة، حيث T هي فئة أصلية لفئة [Integer]. وبسبب هذا القيد، يمكن إضافة الأعداد الصحيحة من 1 إلى 3 إلى القائمة (الأسطر 2–4)، ويمكن استدعاء الطريقة على النحو التالي:
List<Number> numbers=new ArrayList<>();
// ajout double Number <-- Double
numbers.add(7.8);
// ajout Long Number <-- Long
numbers.add(1L);
// ajout List<Number> Number <-- Integer
addNumbers(numbers);
// affichagede List<Number>
for(Number number : numbers){
System.out.println(number.getClass().getName());
}
ينتج عن هذا النتيجة التالية:
لتوسيع فئة Box<T>، سنكتب:
class OtherBox<T> extends Box<T> {
}
يمكن للفئة الفرعية أن تقدم بنفسها معلمات عامة جديدة:
class AnotherBox<T, T1> extends Box<T> {
void consumer(T1 t1) {
System.out.printf("%s%n", t1);
}
}
يمكن للواجهة أيضًا استخدام المعلمات العامة:
interface I<T> {
void doSomethingWith(T t);
}
لتنفيذها، استخدم الصيغة التالية:
class A<T> implements I<T> {
@Override
public void doSomethingWith(T t) {
System.out.printf("%s%n", t);
}
}
نحن الآن نعرف ما يكفي عن العناصر العامة للتعامل مع دوال لامدا.


