التعدادات والسجلّات والأنواع المختومة

مطابقة الأنماط مع switch

15 دقيقة الدرس 9 من 13

مطابقة الأنماط مع switch

أضافت Java 21 (بعد مرحلة معاينة في الإصدارات 17–20) مطابقة الأنماط مع switch — وهي من أكثر الإضافات تعبيريةً في اللغة منذ سنوات. إذا جمعتها مع التسلسلات الهرمية المختومة، أمكنك كتابة كود موجز وشامل لجميع الحالات ويتحقّق منه المُترجم تلقائيًا، وهو ما كان يستلزم سابقًا سلاسل طويلة من فحوصات instanceof والتحويلات النوعية.

المشكلة قبل مطابقة الأنماط

افترض أنّك تملك تسلسلًا هرميًا مختومًا Shape من الدرس السابق:

sealed interface Shape permits Circle, Rectangle, Triangle {} record Circle(double radius) implements Shape {} record Rectangle(double width, double height) implements Shape {} record Triangle(double base, double height) implements Shape {}

قبل Java 21 كانت حساب المساحة تستلزم سلسلة من فحوصات instanceof:

static double area(Shape s) { if (s instanceof Circle c) { return Math.PI * c.radius() * c.radius(); } else if (s instanceof Rectangle r) { return r.width() * r.height(); } else if (s instanceof Triangle t) { return 0.5 * t.base() * t.height(); } else { throw new IllegalStateException("Unknown shape: " + s); } }

يعمل هذا الكود، لكنّ المُترجم لا يُخبرك إن فاتتك حالة ما. وelse الأخيرة استثناء وقت التشغيل عليك تذكّره بنفسك، كما أنّ الكود لا يتوسّع جيدًا مع نموّ التسلسل الهرمي.

أنماط الأنواع في switch

تستبدل مطابقة الأنماط مع switch تلك السلسلة بتعبير واحد:

static double area(Shape s) { return switch (s) { case Circle c -> Math.PI * c.radius() * c.radius(); case Rectangle r -> r.width() * r.height(); case Triangle t -> 0.5 * t.base() * t.height(); }; }

كل case الآن يحمل نمط نوع: النوع المطلوب مطابقته متبوعًا بمتغيّر ربط. إن تطابقت القيمة، يُربط المتغيّر ويصبح متاحًا في نطاق تلك الفرع. لاحظ غياب الفرع الافتراضي — فالمُترجم يعلم أنّ الـ switch شامل لأنّ Shape مختوم وجميع الأنواع المسموح بها مغطّاة.

يُتحقَّق من الشمولية وقت الترجمة. إن حذفت حالة Triangle سيصدر المُترجم خطأً: "لا يغطّي تعبير switch جميع القيم المدخلة الممكنة". هذه الميزة الجوهرية مقارنةً بسلسلة instanceof القديمة — لا يمكنك نسيان نوع فرعي عن طريق الخطأ.

الأنماط المحمية (جمل when)

يمكنك إضافة شرط منطقي لأيّ حالة بكلمة when:

static String classify(Shape s) { return switch (s) { case Circle c when c.radius() > 100 -> "Large circle"; case Circle c -> "Small circle"; case Rectangle r when r.width() == r.height() -> "Square"; case Rectangle r -> "Rectangle"; case Triangle t -> "Triangle"; }; }

تُطابَق الحالات المحمية من الأعلى للأسفل. الدائرة الكبيرة تطابق الفرع الأول؛ الصغيرة تنزل للثاني. الترتيب يهمّ عندما تتداخل الشروط.

ضع الحالات الأكثر تحديدًا (المحمية) أولًا. الحالة غير المحمية تعمل كمصيدة شاملة لذلك النوع. إن وضعت case Circle c غير المحمية قبل المحمية، تصبح المحمية غير قابلة للوصول وسيُحذّرك المُترجم.

التعامل مع null في switch

تقليديًا، تمرير null لعبارة switch يُلقي NullPointerException. مع مطابقة الأنماط يمكنك التعامل معه صراحةً:

static String describe(Shape s) { return switch (s) { case null -> "No shape provided"; case Circle c -> "Circle with radius " + c.radius(); case Rectangle r -> "Rectangle " + r.width() + " x " + r.height(); case Triangle t -> "Triangle"; }; }

بدون case null، ستظلّ الوسيطة null تُلقي NPE. إضافتها يجعل النية واضحة ويتجنّب المفاجآت.

الجمع مع التعدادات

مطابقة الأنماط تعمل أيضًا مع التعدادات، والمُترجم يُطبّق الشمولية عليها كذلك:

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } static String kind(Day d) { return switch (d) { case MON, TUE, WED, THU, FRI -> "Weekday"; case SAT, SUN -> "Weekend"; }; }

إن أُضيفت لاحقًا ثابتة جديدة للتعداد Day، فإنّ كلّ عبارة switch عليه لا تحوي default ستُخفق في الترجمة — شبكة أمان مفيدة يُطبّقها المُترجم.

أنماط التفكيك (معاينة)

Java 21 تُعاين أيضًا أنماط تفكيك السجلّات في switch، تتيح لك المطابقة والتفكيك في خطوة واحدة:

// يتطلّب --enable-preview في Java 21 static String describeRecord(Shape s) { return switch (s) { case Circle(double r) -> "Circle r=" + r; case Rectangle(double w, double h) -> w + " x " + h; case Triangle(double b, double h) -> "base=" + b; }; }
تفكيك السجلّات في switch لا يزال في مرحلة معاينة حتى Java 21 ويستلزم --enable-preview. استخدم شكل متغيّر الربط (case Circle c -> c.radius()) في الكود الإنتاجي الآن.

عبارة switch مقابل تعبير switch

كل ما سبق يستخدم تعابير switch (بأذرع -> وقيمة مُعادة). مطابقة الأنماط تعمل أيضًا في عبارات switch (بأذرع : وbreak)، لكنّ الشكل التعبيري مُفضَّل بشدّة: يُتحقَّق منه شمولية ويُعيد قيمة مباشرةً، ممّا يُلغي الحاجة لمتغيّر محلّي قابل للتعديل.

الخلاصة

تجمع مطابقة الأنماط مع switch فحص النوع والتحويل والربط في بنية واحدة شاملة. حين تُقرن مع الأصناف المختومة والسجلّات، يضمن المُترجم معالجة كلّ نوع فرعي، وتتيح لك الشروط التعبير عن حالات دقيقة دون سلاسل if متداخلة. يشكّل هذا الثلاثي — الأنواع المختومة، والسجلّات، ومطابقة الأنماط — النهج الحديث في Java للبرمجة الموجّهة بالبيانات الآمنة نوعيًا.