Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Java

Java Generics Tutorial - Generics คืออะไรและใช้งานอย่างไร

Java Generics เป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดของภาษา Java แนวคิดเบื้องหลังยาชื่อสามัญนั้นค่อนข้างง่าย อย่างไรก็ตาม บางครั้งก็ดูซับซ้อนเนื่องจากการเปลี่ยนจากไวยากรณ์ปกติที่เกี่ยวข้อง

จุดประสงค์ของบทช่วยสอนนี้คือเพื่อแนะนำให้คุณรู้จักกับแนวคิดที่เป็นประโยชน์เกี่ยวกับยาชื่อสามัญในลักษณะที่เข้าใจง่าย

แต่ก่อนที่จะเจาะลึกถึงยาสามัญ เรามาทำความเข้าใจกันว่าทำไมถึงต้องการยาชื่อสามัญของ Java ก่อน

วัตถุประสงค์ของ Java Generics

ก่อนเริ่มใช้ generics ใน Java 5 คุณสามารถเขียนและคอมไพล์ข้อมูลโค้ดแบบนี้ได้โดยไม่ต้องแสดงข้อผิดพลาดหรือคำเตือน:

List list = new ArrayList();
list.add("hey");
list.add(new Object());

คุณสามารถเพิ่มค่าประเภทใดก็ได้ในรายการหรือ Java Collection อื่นโดยไม่ต้องประกาศประเภทของข้อมูลที่จัดเก็บ แต่เมื่อคุณดึงค่าจากรายการ คุณจะต้องแปลงเป็นค่าบางประเภทอย่างชัดเจน

พิจารณาซ้ำจากรายการด้านบน

for (int i=0; i< list.size(); i++) {
    String value = (String) list.get(i);  //CastClassException when i=1
}

การอนุญาตให้สร้างรายการโดยไม่ประกาศประเภทข้อมูลที่เก็บไว้ก่อน เช่นที่เราทำ อาจส่งผลให้โปรแกรมเมอร์ทำผิดพลาดเหมือนข้างบนซึ่งส่ง ClassCastExceptions ระหว่างรันไทม์

มีการแนะนำ Generics เพื่อป้องกันไม่ให้โปรแกรมเมอร์ทำผิดพลาดดังกล่าว

เมื่อใช้ generics คุณสามารถประกาศประเภทข้อมูลที่จะจัดเก็บได้อย่างชัดเจนเมื่อสร้าง Java Collection ตามตัวอย่างต่อไปนี้

หมายเหตุ:คุณยังสามารถสร้างวัตถุคอลเลกชัน Java ได้โดยไม่ต้องระบุประเภทข้อมูลที่เก็บไว้ แต่ไม่แนะนำ
List<String> stringList = new ArrayList<>();

ตอนนี้ คุณไม่สามารถจัดเก็บจำนวนเต็มในรายการประเภทสตริงโดยไม่ได้ตั้งใจโดยไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์ เพื่อให้แน่ใจว่าโปรแกรมของคุณไม่มีข้อผิดพลาดรันไทม์

stringList.add(new Integer(4)); //Compile time Error

จุดประสงค์หลักของการแนะนำชื่อสามัญใน Java คือการหลีกเลี่ยงการเรียกใช้ ClassCastExceptions ระหว่างรันไทม์

การสร้าง Java Generics

คุณสามารถใช้ generics เพื่อสร้างคลาส Java และเมธอด มาดูตัวอย่างการสร้างชื่อสามัญแต่ละประเภทกัน

คลาสทั่วไป

เมื่อสร้างคลาสทั่วไป พารามิเตอร์ type สำหรับคลาสจะถูกเพิ่มที่ส่วนท้ายของชื่อคลาสภายในมุม <> วงเล็บ

public class GenericClass<T> {
    private T item;
    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return this.item;
    }
}

ที่นี่ T เป็นพารามิเตอร์ชนิดข้อมูล T , N และ E คือตัวอักษรบางตัวที่ใช้สำหรับพารามิเตอร์ชนิดข้อมูลตามข้อตกลงของ Java

ในตัวอย่างข้างต้น คุณสามารถส่งผ่านประเภทข้อมูลเฉพาะเมื่อสร้างวัตถุ GenericClass

public static void main(String[] args) {

    GenericClass<String> gc1 = new GenericClass<>();
    gc1.setItem("hello");
    String item1 = gc1.getItem(); // "hello"
    gc1.setItem(new Object()); //Error

    GenericClass<Integer> gc2 = new GenericClass<>();
    gc2.setItem(new Integer(1));
    Integer item2 = gc2.getItem(); // 1
    gc2.setItem("hello"); //Error
}

คุณไม่สามารถส่งผ่านชนิดข้อมูลดั้งเดิมไปยังพารามิเตอร์ชนิดข้อมูลเมื่อสร้างอ็อบเจ็กต์คลาสทั่วไป เฉพาะประเภทข้อมูลที่ขยายประเภทอ็อบเจ็กต์เท่านั้นที่สามารถส่งผ่านเป็นพารามิเตอร์ประเภทได้

ตัวอย่างเช่น:

GenericClass<float> gc3 = new GenericClass<>(); //Error

วิธีการทั่วไป

การสร้างเมธอดทั่วไปมีรูปแบบที่คล้ายคลึงกันในการสร้างคลาสทั่วไป คุณสามารถใช้เมธอดทั่วไปในคลาสทั่วไปและคลาสที่ไม่ใช่แบบทั่วไปได้

public class GenericMethodClass {

    public static <T> void printItems(T[] arr){
        for (int i=0; i< arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public static void main(String[] args) {
        String[] arr1 = {"Cat", "Dog", "Mouse"};
        Integer[] arr2 = {1, 2, 3};

        GenericMethodClass.printItems(arr1); // "Cat", "Dog", "Mouse"
        GenericMethodClass.printItems(arr2); // 1, 2, 3
    }
}

ที่นี่ คุณสามารถส่งอาร์เรย์ของประเภทเฉพาะเพื่อกำหนดพารามิเตอร์ของเมธอดได้ วิธีการทั่วไป PrintItems() วนซ้ำผ่านอาร์เรย์ที่ส่งผ่านและพิมพ์รายการที่เก็บไว้เหมือนกับวิธี Java ปกติ

พารามิเตอร์ประเภทที่ถูกผูกไว้

จนถึงตอนนี้ คลาสทั่วไปและเมธอดที่เราสร้างขึ้นข้างต้นสามารถกำหนดพารามิเตอร์เป็นชนิดข้อมูลใดๆ ก็ได้ที่ไม่ใช่ประเภทดั้งเดิม แต่ถ้าเราต้องการจำกัดประเภทข้อมูลที่สามารถส่งต่อไปยังยาสามัญได้ล่ะ นี่คือที่มาของพารามิเตอร์ประเภทที่มีขอบเขต

คุณสามารถผูกประเภทข้อมูลที่ยอมรับโดยคลาสหรือเมธอดทั่วไปโดยระบุว่าควรเป็นคลาสย่อยของประเภทข้อมูลอื่น

ตัวอย่างเช่น:

//accepts only subclasses of List
public class UpperBoundedClass<T extends List>{
    //accepts only subclasses of List
    public <T extends List> void UpperBoundedMethod(T[] arr) {
    }
}

ที่นี่ UpperBoundedClass และ UpperBoundedMethod สามารถกำหนดพารามิเตอร์ได้โดยใช้ประเภทย่อยของ List ชนิดข้อมูล

List ชนิดข้อมูลทำหน้าที่เป็นขอบเขตบนของพารามิเตอร์ประเภท หากคุณพยายามใช้ประเภทข้อมูลที่ไม่ใช่ประเภทย่อยของ List มันจะส่งข้อผิดพลาดเวลาคอมไพล์

ขอบเขตไม่ได้จำกัดเฉพาะคลาสเท่านั้น คุณสามารถส่งผ่านอินเทอร์เฟซได้เช่นกัน ในกรณีนี้ การขยายอินเทอร์เฟซหมายถึงการนำอินเทอร์เฟซไปใช้

พารามิเตอร์ยังสามารถมีหลายขอบเขตได้ดังตัวอย่างนี้

//accepts only subclasses of both Mammal and Animal
public class MultipleBoundedClass<T extends Mammal & Animal>{

    //accepts only subclasses of both Mammal and Animal
    public <T extends Mammal & Animal> void MultipleBoundedMethod(T[] arr){

    }
}

ชนิดข้อมูลที่ยอมรับต้องเป็นคลาสย่อยของทั้งสัตว์และสัตว์เลี้ยงลูกด้วยนม หากหนึ่งในขอบเขตเหล่านี้เป็นคลาส ขอบเขตนั้นต้องมาก่อนในการประกาศขอบเขต

ในตัวอย่างข้างต้น หาก Mammal เป็นคลาสและ Animal เป็นส่วนต่อประสาน สัตว์เลี้ยงลูกด้วยนมจะต้องมาก่อนดังที่แสดงด้านบน มิฉะนั้น รหัสจะแสดงข้อผิดพลาดในการคอมไพล์

สัญลักษณ์แทน Java Generics

ไวด์การ์ดใช้เพื่อส่งต่อพารามิเตอร์ของประเภททั่วไปไปยังเมธอด ซึ่งแตกต่างจากวิธีการทั่วไป ในที่นี้ พารามิเตอร์ทั่วไปจะถูกส่งไปยังพารามิเตอร์ที่ยอมรับโดยวิธีการ ซึ่งแตกต่างจากพารามิเตอร์ประเภทข้อมูลที่เรากล่าวถึงข้างต้น สัญลักษณ์แทนถูกแสดงโดย ? สัญลักษณ์

public void printItems(List<?> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

printItems() above ด้านบน method ยอมรับรายการประเภทข้อมูลใด ๆ เป็นพารามิเตอร์ ซึ่งจะป้องกันไม่ให้โปรแกรมเมอร์ต้องทำซ้ำรหัสสำหรับรายการประเภทข้อมูลต่างๆ ซึ่งจะเป็นกรณีนี้หากไม่มีข้อมูลทั่วไป

สัญลักษณ์แทนด้านบน

หากเราต้องการจำกัดประเภทข้อมูลที่จัดเก็บในรายการที่เมธอดยอมรับ เราก็สามารถใช้สัญลักษณ์แทนที่มีขอบเขตได้

ตัวอย่าง:

public void printSubTypes(List<? extends Color> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

printSubTypes() method ยอมรับเฉพาะรายการที่จัดเก็บประเภทย่อยของ Color ยอมรับรายการวัตถุ RedColor หรือ BlueColor แต่ไม่ยอมรับรายการวัตถุ Animal เนื่องจากสัตว์ไม่ใช่ประเภทย่อยของสี นี่คือตัวอย่างของสัญลักษณ์แทนขอบเขตบน

ไวด์การ์ดที่มีขอบเขตล่าง

ในทำนองเดียวกัน ถ้าเรามี:

public void printSuperTypes(List<? super Dog> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

จากนั้น printSuperTypes() method ยอมรับเฉพาะรายการที่จัดเก็บ super types ของ Dog class มันจะยอมรับรายการของ Mammal หรือ Animal object แต่ไม่ใช่รายการของ LabDog object เนื่องจาก LabDog ไม่ใช่ superclass ของ Dog แต่เป็น subclass นี่คือตัวอย่างของไวด์การ์ดที่มีขอบเขตล่าง

บทสรุป

Java Generics ได้กลายเป็นคุณลักษณะที่โปรแกรมเมอร์ขาดไม่ได้ตั้งแต่เปิดตัว

ความนิยมนี้เกิดจากการทำให้ชีวิตของโปรแกรมเมอร์ง่ายขึ้น นอกเหนือจากการป้องกันไม่ให้เกิดข้อผิดพลาดในการเขียนโค้ดแล้ว การใช้ generics ทำให้โค้ดซ้ำกันน้อยลง คุณสังเกตไหมว่ามันทำให้คลาสและวิธีการทั่วไปเพื่อหลีกเลี่ยงไม่ให้ต้องโค้ดซ้ำสำหรับข้อมูลประเภทต่างๆ

การมีความเข้าใจที่ดีเกี่ยวกับยาสามัญเป็นสิ่งสำคัญในการเป็นผู้เชี่ยวชาญในภาษา ดังนั้นการนำสิ่งที่คุณเรียนรู้ในบทช่วยสอนนี้ไปใช้ในโค้ดที่ใช้งานได้จริงจึงเป็นหนทางสู่อนาคต