0Pricing
Java Academy · درس

مبدأ PECS

Producer Extends وConsumer Super

مبدأ PECS درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

ما الذي تعنيه PECS

تعني PECS العبارة Producer Extends, Consumer Super. وهي وسيلة تذكّر وضعها Joshua Bloch في كتاب Effective Java.

  • إذا كانت المعلمة تنتج T (أي تقرأ منها)، فاستخدم ? extends T.
  • إذا كانت المعلمة تستهلك T (أي تكتب فيها)، فاستخدم ? super T.
public class Main {
    public static void main(String[] args) {
        System.out.println("Producer Extends, Consumer Super");
    }
}

طريقة copy التقليدية

المثال الكلاسيكي على PECS هو طريقة copy. فـالمصدر منتِج، ولذلك نوعه ? extends T. أما الوجهة فمستهلِكة، ولذلك نوعها ? super T.

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

public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3);
        List<Object> dest = new ArrayList<>();
        copy(src, dest);
        System.out.println(dest);
    }
}

لماذا يضيف محرفا البدل مرونة

بفضل PECS، تعمل copy مع تركيبات عديدة من الأنواع: مصدر من نوع Integer إلى وجهة من نوع Number أو Object، ومصدر من نوع String إلى وجهة من نوع CharSequence.

من دون محارف البدل، كان سيتعين أن تتطابق الأنواع تمامًا، مما كان سيحد بشدة من إعادة الاستخدام.

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

public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Integer> ints = List.of(10, 20);
        List<Number> nums = new ArrayList<>();
        copy(ints, nums);
        System.out.println(nums);
    }
}

مثال على منتِج

يجب أن تستخدم الطريقة التي تقرأ من مجموعتها فقط extends. نجمع هنا قيم منتِج.

import java.util.List;

public class Main {
    static double sum(List<? extends Number> producer) {
        double total = 0;
        for (Number n : producer) total += n.doubleValue();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.1, 2.2)));
    }
}

مثال على مستهلِك

يجب أن تستخدم الطريقة التي تكتب إلى مجموعتها فقط super. ندفع هنا قيمًا إلى مستهلِك.

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

public class Main {
    static void pushDigits(List<? super Integer> consumer) {
        for (int i = 0; i < 5; i++) consumer.add(i);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        pushDigits(nums);
        System.out.println(nums);
    }
}

دوران معًا: استخدم نوعًا عاديًا

إذا كانت المعلمة منتِجًا ومستهلِكًا معًا، فيجب ألا تستخدم محرف بدل.

تحتاج الطريقة التي تقرأ القائمة نفسها وتكتب فيها إلى النوع المحدد T.

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

public class Main {
    static <T> void rotate(List<T> list) {
        if (list.isEmpty()) return;
        T last = list.remove(list.size() - 1);
        list.add(0, last);
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
        rotate(list);
        System.out.println(list);
    }
}

تطبيق PECS في Comparator

يطبّق JDK قاعدة PECS على نطاق واسع. تستقبل Collections.max قيمة Comparator<? super T> لأن المقارن يستهلك قيم T لمقارنتها.

import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Comparator<Object> byHash = Comparator.comparingInt(Object::hashCode);
        List<String> words = List.of("a", "bb", "ccc");
        String max = java.util.Collections.max(words, byHash);
        System.out.println(max);
    }
}

تطبيق PECS في Stream.collect

تتبع العديد من الواجهات الوظيفية قاعدة PECS. إذ يمكن لـ Consumer<? super T> قبول معالج مكتوب لنوع أوسع.

import java.util.List;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        Consumer<Object> printer = o -> System.out.println("got: " + o);
        List<String> items = List.of("x", "y");
        items.forEach(printer); // forEach takes Consumer<? super T>
    }
}

المنتِجات والمستهلِكات في Function

Function<? super T, ? extends R> هي الصيغة الكاملة لقاعدة PECS: المدخل مستهلَك (super)، والمخرج منتَج (extends).

يتيح ذلك للتحويل قبول مدخلات أوسع وإرجاع مخرجات أضيق.

import java.util.function.Function;

public class Main {
    static <T, R> R apply(Function<? super T, ? extends R> fn, T value) {
        return fn.apply(value);
    }
    public static void main(String[] args) {
        Function<Object, Integer> len = o -> o.toString().length();
        System.out.println(apply(len, "hello"));
    }
}

أنواع الإرجاع: تجنّب محارف البدل

لا تستخدم محارف البدل في أنواع الإرجاع. فهي تجبر المستدعين على التعامل مع محارف البدل أيضًا، مما يسرّب التعقيد إليهم.

أرجع نوعًا محددًا، وأبقِ محارف البدل في المعلمات حيث تضيف مرونة.

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

public class Main {
    // Good: concrete return type
    static List<Number> doubled(List<? extends Number> src) {
        List<Number> out = new ArrayList<>();
        for (Number n : src) out.add(n.doubleValue() * 2);
        return out;
    }
    public static void main(String[] args) {
        System.out.println(doubled(List.of(1, 2, 3)));
    }
}

دمج كامل باستخدام PECS

بتجميع كل شيء: عملية دمج تقرأ من منتِجين وتكتب في مستهلِك واحد.

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

public class Main {
    static <T> void merge(List<? extends T> a, List<? extends T> b, List<? super T> out) {
        for (T t : a) out.add(t);
        for (T t : b) out.add(t);
    }
    public static void main(String[] args) {
        List<Integer> a = List.of(1, 2);
        List<Integer> b = List.of(3, 4);
        List<Number> out = new ArrayList<>();
        merge(a, b, out);
        System.out.println(out);
    }
}

تحقق سريع

طبّق قاعدة PECS.

مراجعة

لقد تعلمت مبدأ PECS:

  • Producer Extends: تستخدم المعلمات المخصّصة للقراءة فقط ? extends T.
  • Consumer Super: تستخدم المعلمات المخصّصة للكتابة فقط ? super T.
  • تستخدم المعلمات ذات الدورين معلمة نوع عادية.
  • تجنّب محارف البدل في أنواع الإرجاع.

بعد ذلك، سنطبّق هذه الأفكار على تصميم واجهات برمجة تطبيقات حقيقية.

public class Main {
    public static void main(String[] args) {
        System.out.println("PECS recap: Producer Extends, Consumer Super");
    }
}

الأسئلة الشائعة

هل درس «مبدأ PECS» مجاني؟

نعم — نص درس «مبدأ PECS» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «مبدأ PECS»؟

Producer Extends وConsumer Super تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «مبدأ PECS»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. Wildcards ذات الحد العلوي
  2. Wildcards ذات الحد السفلي
  3. مبدأ PECS
  4. Wildcards في واجهات البرمجة
← العودة إلى Java Academy