أهلاً! في درس اليوم، سنواصل دراسة الأدوية الجنيسة. في الواقع، هذا موضوع كبير، ولكن لا يمكن تجنبه - فهو جزء مهم للغاية من اللغة :) عندما تدرس وثائق Oracle حول الأدوية العامة أو تقرأ البرامج التعليمية عبر الإنترنت، سوف تصادف مصطلحات الأنواع غير القابلة للتكرار و أنواع قابلة للتجسيد . النوع القابل لإعادة البناء هو النوع الذي تتوفر معلوماته بشكل كامل في وقت التشغيل. في Java، تتضمن هذه الأنواع الأنواع الأولية والأنواع الأولية والأنواع غير العامة. في المقابل، الأنواع غير القابلة للإصلاح هي أنواع يتم مسح معلوماتها ويصبح من غير الممكن الوصول إليها في وقت التشغيل. كما يحدث، هذه أدوية عامة —
List<String>
،،، List<Integer>
وما إلى ذلك.
بالمناسبة، هل تتذكر ما هو varargs؟
في حالة نسيانك، فهذه وسيطة ذات طول متغير. إنها مفيدة في المواقف التي لا نعرف فيها عدد الوسائط التي يمكن تمريرها إلى طريقتنا. على سبيل المثال، إذا كان لدينا فئة آلة حاسبة لديهاsum
طريقة. يمكن أن تستقبل الطريقة sum()
رقمين، أو 3، أو 5، أو أي عدد تريده. سيكون من الغريب جدًا تحميل sum()
الطريقة بشكل زائد لكل عدد ممكن من الوسائط. بدلا من ذلك، يمكننا أن نفعل هذا:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
إخراج وحدة التحكم:
15
11
يوضح لنا هذا أن هناك بعض الميزات المهمة عند استخدام varargs مع الأدوية الجنيسة. دعونا نلقي نظرة على الكود التالي:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
addAll()
تأخذ الطريقة كمدخل أي List<E>
عدد من E
الكائنات، ثم تضيف كل هذه الكائنات إلى القائمة. في هذه main()
الطريقة، نسمي طريقتنا addAll()
مرتين. في الحالة الأولى، نضيف سلسلتين عاديتين إلى ملف List
. كل شيء على ما يرام هنا. وفي الحالة الثانية، نضيف كائنين Pair<String, String>
إلى List
. ولكن هنا نتلقى تحذيرًا بشكل غير متوقع:
Unchecked generics array creation for varargs parameter
ماذا يعني ذالك؟ لماذا نتلقى تحذيرًا ولماذا يوجد أي ذكر لـ array
؟ بعد كل شيء، الكود الخاص بنا لا يحتوي على array
! لنبدأ بالحالة الثانية. يشير التحذير إلى صفيف لأن المترجم يحول وسيطة الطول المتغير (varargs) إلى صفيف. بمعنى آخر، توقيع طريقتنا addAll()
هو:
public static <E> void addAll(List<E> list, E... array)
في الواقع يبدو مثل هذا:
public static <E> void addAll(List<E> list, E[] array)
أي أنه في هذه main()
الطريقة، يقوم المترجم بتحويل الكود الخاص بنا إلى هذا:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
مجموعة String
على ما يرام. ولكن Pair<String, String>
مجموعة ليست كذلك. المشكلة هي أن Pair<String, String>
هذا النوع غير قابل للإصلاح. أثناء الترجمة، يتم مسح كافة المعلومات حول وسائط النوع (<String, String>). لا يُسمح بإنشاء صفائف من نوع غير قابل للإعادة في Java . يمكنك رؤية ذلك إذا حاولت إنشاء مصفوفة Pair<String, String> يدويًا
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
السبب واضح: نوع الأمان. كما تتذكر، عند إنشاء مصفوفة، تحتاج بالتأكيد إلى تحديد الكائنات (أو الأوليات) التي سيتم تخزينها في المصفوفة.
int array[] = new int[10];
في أحد دروسنا السابقة، قمنا بدراسة محو الكتابة بالتفصيل. في هذه الحالة، يؤدي محو الكتابة إلى فقدان المعلومات التي Pair
تخزنها الكائنات <String, String>
في أزواج. سيكون إنشاء المصفوفة غير آمن. عند استخدام الأساليب التي تتضمن varargs والأسماء العامة، تأكد من تذكر محو الكتابة وكيفية عمله. إذا كنت متأكدًا تمامًا من الكود الذي كتبته وتعرف أنه لن يسبب أي مشاكل، فيمكنك إيقاف تشغيل التحذيرات المتعلقة بـ varargs باستخدام التعليقات التوضيحية @SafeVarargs
.
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
إذا قمت بإضافة هذا التعليق التوضيحي إلى طريقتك، فلن يظهر التحذير الذي واجهناه سابقًا. هناك مشكلة أخرى يمكن أن تحدث عند استخدام varargs مع الأدوية الجنيسة وهي تلوث الكومة. يمكن أن يحدث تلوث كومة في الحالة التالية:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
إخراج وحدة التحكم:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
بعبارات بسيطة، يحدث تلوث الكومة عندما A
يجب أن تكون كائنات النوع في الكومة، لكن كائنات النوع B
تنتهي هناك بسبب أخطاء تتعلق بسلامة الكتابة. في مثالنا، هذا هو بالضبط ما يحدث. أولاً، قمنا بإنشاء المتغير الخام numbers
وتخصيص مجموعة عامة ( ArrayList<Number>
) له. ثم أضفنا الرقم 1
إلى المجموعة.
List<String> strings = numbers;
في هذا السطر، حاول المترجم تحذيرنا من الأخطاء المحتملة عن طريق إصدار تحذير " مهمة لم يتم التحقق منها... "، لكننا تجاهلناه. لقد انتهى بنا الأمر بمتغير عام من النوع List<String>
يشير إلى مجموعة عامة من النوع ArrayList<Number>
. ومن الواضح أن هذا الوضع يمكن أن يؤدي إلى مشاكل! وهكذا يحدث. باستخدام المتغير الجديد، نضيف سلسلة إلى المجموعة. لدينا الآن تلوث كومة - أضفنا رقمًا ثم سلسلة إلى المجموعة ذات المعلمات. حذرنا المترجم، لكننا تجاهلنا تحذيره. ونتيجة لذلك، نحصل على ClassCastException
فقط أثناء تشغيل البرنامج. إذن ما علاقة هذا بالفارارج؟ يمكن أن يؤدي استخدام varargs مع الأدوية الجنيسة بسهولة إلى تلوث الكومة. إليك مثال بسيط:
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
polluteHeap(cars1, cars2);
}
}
ما الذي يحدث هنا؟ بسبب محو النوع، وسيطتنا ذات الطول المتغير
List<String>...stringsLists
يصبح مصفوفة من القوائم، أي List[]
كائنات من نوع غير معروف (لا تنس أن varargs يتحول إلى مصفوفة عادية أثناء التجميع). ولهذا السبب، يمكننا بسهولة تعيينه للمتغير Object[] array
في السطر الأول من الطريقة - تم مسح نوع الكائنات الموجودة في قوائمنا! والآن لدينا Object[]
متغير يمكننا إضافة أي شيء إليه على الإطلاق، حيث أن جميع الكائنات في Java ترث Object
! في البداية، لدينا فقط مجموعة من قوائم السلاسل. ولكن بفضل محو الكتابة واستخدامنا لـ varargs، يمكننا بسهولة إضافة قائمة من الأرقام، وهو ما نقوم به. نتيجة لذلك، نقوم بتلويث الكومة عن طريق خلط كائنات من أنواع مختلفة. ستكون النتيجة أخرى ClassCastException
عندما نحاول قراءة سلسلة من المصفوفة. إخراج وحدة التحكم:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
يمكن أن تحدث مثل هذه العواقب غير المتوقعة عن طريق استخدام varargs، وهي آلية تبدو بسيطة :) وبهذا ينتهي درس اليوم. لا تنس حل بعض المهام، وإذا كان لديك الوقت والطاقة، فادرس بعض القراءة الإضافية. " جافا الفعالة
" لن تقرأ نفسها! :) حتى المرة القادمة!
GO TO FULL VERSION