0Pricing
Java Academy · บทเรียน

หลักการ PECS

ผู้ผลิตใช้ Extends ผู้บริโภคใช้ Super

หลักการ PECS เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

PECS ย่อมาจากอะไร

PECS หมายถึง ผู้ผลิตใช้ขอบเขตบน ผู้บริโภคใช้ขอบเขตล่าง เป็นคำช่วยจำจากหนังสือ Java อย่างมีประสิทธิภาพของ Joshua Bloch

  • หากพารามิเตอร์สร้างค่า 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 จึงรองรับการจับคู่ชนิดข้อมูลได้หลากหลาย เช่น แหล่งข้อมูลจำนวนเต็มไปยังปลายทางชนิดตัวเลขหรือออบเจ็กต์ และแหล่งข้อมูล 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);
    }
}

ตัวอย่างผู้ผลิต

เมธอดที่อ่านจากชุดข้อมูลเพียงอย่างเดียวควรใช้ขอบเขตบน ในที่นี้เราจะหาผลรวมจากผู้ผลิต

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)));
    }
}

ตัวอย่างผู้บริโภค

เมธอดที่เขียนลงในชุดข้อมูลเพียงอย่างเดียวควรใช้ขอบเขตล่าง ในที่นี้เราจะใส่ค่าลงในผู้บริโภค

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 ในตัวเปรียบเทียบ

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<? super T, ? extends R> คือรูปแบบ PECS ที่ครบถ้วน: อินพุตถูกนำไปใช้ (ขอบเขตล่าง) และเอาต์พุตถูกสร้างขึ้น (ขอบเขตบน)

รูปแบบนี้ทำให้การแปลงข้อมูลรับอินพุตที่กว้างกว่าและคืนเอาต์พุตที่จำเพาะกว่าได้

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)));
    }
}

การ merge ตาม PECS แบบครบถ้วน

เมื่อนำทุกอย่างมารวมกัน: การ merge ที่อ่านจากผู้ผลิตสองรายแล้วเขียนลงในผู้บริโภครายเดียว

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 แล้ว:

  • ผู้ผลิตใช้ขอบเขตบน: พารามิเตอร์แบบอ่านอย่างเดียวใช้ ? extends T
  • ผู้บริโภคใช้ขอบเขตล่าง: พารามิเตอร์แบบเขียนอย่างเดียวใช้ ? super T
  • พารามิเตอร์ที่มีทั้งสองบทบาทให้ใช้พารามิเตอร์ชนิดข้อมูลธรรมดา
  • หลีกเลี่ยงไวลด์การ์ดในชนิดผลลัพธ์

ถัดไปคือการนำแนวคิดเหล่านี้ไปใช้ในการออกแบบส่วนติดต่อโปรแกรมจริง

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

คำถามที่พบบ่อย

บทเรียน “หลักการ PECS” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “หลักการ PECS” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “หลักการ PECS”

ผู้ผลิตใช้ Extends ผู้บริโภคใช้ Super คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “หลักการ PECS” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ไวลด์การ์ดขอบเขตบน
  2. ไวลด์การ์ดขอบเขตล่าง
  3. หลักการ PECS
  4. ไวลด์การ์ดใน API
← กลับไปที่ Java Academy