สวัสดี! เราจะพูดถึง Java Generics ฉันต้องบอกว่าคุณจะได้เรียนรู้มากมาย! ไม่เพียงแต่บทเรียนนี้เท่านั้น แต่ยังรวมถึงบทเรียนอื่นๆ อีกสองสามบทถัดไปด้วย ที่จะเน้นไปที่ความรู้ทั่วไป ดังนั้น หากคุณสนใจยาชื่อสามัญ วันนี้เป็นวันโชคดีของคุณ คุณจะได้เรียนรู้มากมายเกี่ยวกับคุณลักษณะของยาชื่อสามัญ และถ้าไม่ลาออกและผ่อนคลาย! :) นี่เป็นหัวข้อที่สำคัญมากและคุณจำเป็นต้องรู้ เริ่มจากสิ่งง่ายๆ: "อะไร" และ "ทำไม"
บทที่ 23 ของหนังสือมีชื่อที่คมคายมาก: "อย่าใช้ประเภทดิบในรหัสใหม่" นี่คือสิ่งที่คุณต้องจำ เมื่อใช้คลาสทั่วไป ห้ามเปลี่ยนประเภท ทั่วไปเป็นประเภทดิบ
ขอให้ประสบความสำเร็จในการเรียน! :)
Java Generic คืออะไร
Generics เป็นประเภทที่มีพารามิเตอร์ เมื่อสร้างประเภททั่วไป คุณไม่เพียงแต่ระบุประเภทเท่านั้น แต่ยังระบุประเภทข้อมูลที่จะใช้ด้วย ฉันเดาว่าตัวอย่างที่ชัดเจนที่สุดอยู่ในใจของคุณแล้ว: ArrayList! นี่คือวิธีที่เรามักจะสร้างขึ้นในโปรแกรม:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> myList1 = new ArrayList<>();
myList1.add("Test String 1");
myList1.add("Test String 2");
}
}
อย่างที่คุณอาจเดา คุณลักษณะของรายการนี้คือเราไม่สามารถยัดทุกอย่างลงในรายการได้: มันใช้งานได้เฉพาะกับอ็อบเจกต์สตริง ตอนนี้เรามาพูดนอกเรื่องเล็กน้อยเกี่ยวกับประวัติของ Java และพยายามตอบคำถามว่า "ทำไม" ในการทำเช่นนี้ เราจะเขียนคลาส ArrayList เวอร์ชันที่เรียบง่ายของเราเอง รายการของเรารู้วิธีเพิ่มข้อมูลและดึงข้อมูลจากอาร์เรย์ภายในเท่านั้น:
public class MyListClass {
private Object[] data;
private int count;
public MyListClass() {
this.data = new Object[10];
this.count = 0;
}
public void add(Object o) {
this.data[count] = o;
count++;
}
public Object[] getData() {
return data;
}
}
สมมติว่าเราต้องการให้ list เก็บเฉพาะInteger s เราไม่ได้ใช้ประเภททั่วไป เราไม่ต้องการรวมการตรวจสอบ "instanceof Integer " ที่ชัดเจนในเมธอดadd() หากเราทำเช่นนั้น ทั้งคลาสของเราจะเหมาะสำหรับInteger เท่านั้น และเราจะต้องเขียนคลาสที่คล้ายกันสำหรับประเภทข้อมูลอื่น ๆ ในโลก! เราจะพึ่งพาโปรแกรมเมอร์ของเรา และเพียงแค่แสดงความคิดเห็นในโค้ดเพื่อให้แน่ใจว่าพวกเขาจะไม่เพิ่มสิ่งที่เราไม่ต้องการ:
// Use this class ONLY with the Integer data type
public void add(Object o) {
this.data[count] = o;
count++;
}
โปรแกรมเมอร์คนหนึ่งพลาดความคิดเห็นนี้และใส่สตริงหลายตัวในรายการตัวเลขโดยไม่ได้ตั้งใจแล้วคำนวณผลรวม:
public class Main {
public static void main(String[] args) {
MyListClass list = new MyListClass();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
System.out.println(sum1);
Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
System.out.println(sum2);
}
}
เอาต์พุตคอนโซล:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at Main.main (Main.java:14)
อะไรคือส่วนที่แย่ที่สุดของสถานการณ์นี้? ไม่ใช่ความประมาทเลินเล่อของโปรแกรมเมอร์อย่างแน่นอน ส่วนที่แย่ที่สุดคือโค้ดที่ไม่ถูกต้องไปอยู่ในสถานที่สำคัญในโปรแกรมของเราและคอมไพล์สำเร็จ ตอนนี้เราจะพบจุดบกพร่องไม่ใช่ขณะเขียนโค้ด แต่พบระหว่างการทดสอบเท่านั้น (และนี่คือสถานการณ์กรณีที่ดีที่สุด!) การแก้ไขจุดบกพร่องในขั้นตอนต่อๆ ไปของการพัฒนามีค่าใช้จ่ายสูงกว่ามาก ทั้งในแง่ของเงินและเวลา นี่คือสิ่งที่ generics เป็นประโยชน์ต่อเรา: คลาส generic ช่วยให้โปรแกรมเมอร์ผู้โชคร้ายตรวจพบข้อผิดพลาดได้ทันที โปรแกรมจะไม่รวบรวม!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> myList1 = new ArrayList<>();
myList1.add(100);
myList1.add(100);
myList1.add ("Lolkek"); // Error!
myList1.add("Shalala"); // Error!
}
}
โปรแกรมเมอร์รู้ทันความผิดพลาดของตนเองและเก่งขึ้นทันที อย่างไรก็ตาม เราไม่ต้องสร้าง คลาส List เอง เพื่อดูข้อผิดพลาดประเภทนี้ เพียงลบวงเล็บมุมแล้วพิมพ์ ( <Integer> ) จาก ArrayList ธรรมดา!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
System.out.println((Integer) list.get(0) + (Integer) list.get(1));
System.out.println((Integer) list.get(2) + (Integer) list.get(3));
}
}
เอาต์พุตคอนโซล:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at Main.main(Main.java:16)
กล่าวอีกนัยหนึ่ง แม้จะใช้กลไก "แบบเนทีฟ" ของ Java เราก็สามารถทำผิดพลาดและสร้างคอลเล็กชันที่ไม่ปลอดภัยได้ อย่างไรก็ตาม หากเราวางโค้ดนี้ลงใน IDE เราจะได้รับคำเตือน: "ไม่ได้เลือกการเรียกเพื่อเพิ่ม (E) ในฐานะสมาชิกของประเภท raw ของ java.util.List" เราได้รับแจ้งว่ามีบางอย่างผิดพลาดเมื่อเพิ่มรายการ ไปยังคอลเลกชันที่ไม่มีประเภททั่วไป แต่คำว่า "ประเภทดิบ" หมายถึงอะไร? ประเภทดิบคือคลาสทั่วไปที่ถูกลบประเภทแล้ว กล่าวอีกนัยหนึ่งList myList1เป็นประเภทข้อมูลดิบ สิ่งที่ตรงกันข้ามกับประเภท rawคือประเภททั่วไป — คลาสทั่วไปที่มีการระบุประเภทพารามิเตอร์ ตัวอย่างเช่นList<String> myList1. คุณอาจถามว่าทำไมภาษาจึงอนุญาตให้ใช้ประเภท raw ? เหตุผลนั้นง่าย ผู้สร้างของ Java ออกจากการสนับสนุนประเภท rawในภาษาเพื่อหลีกเลี่ยงการสร้างปัญหาความเข้ากันได้ เมื่อถึงเวลาที่ Java 5.0 เปิดตัว (ชื่อสามัญปรากฏตัวครั้งแรกในเวอร์ชันนี้) มีการเขียนโค้ดจำนวนมากโดยใช้ประเภท rawแล้ว เป็นผลให้กลไกนี้ยังคงรองรับมาจนถึงทุกวันนี้ เราได้กล่าวถึงหนังสือคลาสสิกของ Joshua Bloch เรื่อง "Effective Java" ซ้ำแล้วซ้ำเล่าในบทเรียน ในฐานะหนึ่งในผู้สร้างภาษา เขาไม่ได้ข้ามประเภทดิบและประเภททั่วไปในหนังสือของเขา 
วิธีการทั่วไป
Java ให้คุณกำหนดพารามิเตอร์แต่ละเมธอดโดยสร้างสิ่งที่เรียกว่าเมธอดทั่วไป วิธีการดังกล่าวมีประโยชน์อย่างไร? เหนือสิ่งอื่นใด พวกมันมีประโยชน์ตรงที่ช่วยให้คุณทำงานกับพารามิเตอร์เมธอดประเภทต่างๆ หากตรรกะเดียวกันสามารถนำไปใช้กับประเภทต่างๆ ได้อย่างปลอดภัย วิธีทั่วไปอาจเป็นทางออกที่ดี ลองพิจารณาตัวอย่างง่ายๆ นี้: สมมติว่าเรามีรายการชื่อmyList1 เราต้องการลบค่าทั้งหมดออกจากรายการและเติมช่องว่างทั้งหมดด้วยค่าใหม่ นี่คือลักษณะของชั้นเรียนของเราด้วยวิธีการทั่วไป:
public class TestClass {
public static <T> void fill(List<T> list, T val) {
for (int i = 0; i < list.size(); i++)
list.set(i, val);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Old String 1");
strings.add("Old String 2");
strings.add("Old String 3");
fill(strings, "New String");
System.out.println(strings);
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
fill(numbers, 888);
System.out.println(numbers);
}
}
ให้ความสนใจกับไวยากรณ์ มันดูผิดปกติเล็กน้อย:
public static <T> void fill(List<T> list, T val)
เราเขียน <T> ก่อนประเภทการส่งคืน สิ่งนี้บ่งชี้ว่าเรากำลังจัดการกับวิธีการทั่วไป ในกรณีนี้ เมธอดจะรับพารามิเตอร์ 2 ตัวเป็นอินพุต: รายการของอ็อบเจกต์ T และอ็อบเจกต์ T ที่แยกจากกัน เมื่อใช้ <T> เรากำหนดพารามิเตอร์ของประเภทพารามิเตอร์ของเมธอด: เราไม่สามารถผ่านรายการสตริงและจำนวนเต็มได้ รายการของสตริงและสตริง รายการของจำนวนเต็มและจำนวนเต็ม รายการของ วัตถุ Cat ของเราเอง และ วัตถุ Cat อื่น นั่นคือสิ่งที่เราต้องทำ เมธอดmain()แสดงให้เห็นว่า เมธอด fill()สามารถใช้ทำงานกับข้อมูลประเภทต่างๆ ได้ อย่างไร ขั้นแรก เราใช้วิธีที่มีรายการสตริงและสตริงเป็นอินพุต จากนั้นตามด้วยรายการจำนวนเต็มและจำนวนเต็ม เอาต์พุตคอนโซล:
[New String, New String, New String] [888, 888, 888]
ลองนึกภาพถ้าเราไม่มีเมธอดทั่วไปและต้องการตรรกะของ เมธอด fill()สำหรับ 30 คลาสที่แตกต่างกัน เราจะต้องเขียนวิธีเดียวกัน 30 ครั้งสำหรับประเภทข้อมูลที่แตกต่างกัน! แต่ด้วยวิธีการทั่วไป เราสามารถนำรหัสของเรากลับมาใช้ใหม่ได้! :)
ชั้นเรียนทั่วไป
คุณไม่จำกัดเฉพาะคลาสทั่วไปที่มีให้ในไลบรารี Java มาตรฐาน — คุณสามารถสร้างคลาสของคุณเองได้! นี่คือตัวอย่างง่ายๆ:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Old String");
System.out.println(stringBox.get());
stringBox.set("New String");
System.out.println(stringBox.get());
stringBox.set(12345); // Compilation error!
}
}
คลาส Box<T> ของเราเป็นคลาสทั่วไป เมื่อเรากำหนดประเภทข้อมูล ( <T> ) ระหว่างการสร้าง เราจะไม่สามารถวางวัตถุประเภทอื่นในนั้นได้อีก สามารถดูได้จากตัวอย่าง เมื่อสร้างออบเจกต์ของเรา เราระบุว่ามันจะทำงานกับสตริงได้:
Box<String> stringBox = new Box<>();
และในโค้ดบรรทัดสุดท้าย เมื่อเราพยายามใส่หมายเลข 12345 ในช่อง เราได้รับข้อผิดพลาดในการคอมไพล์! มันง่ายมาก! เราได้สร้างคลาสทั่วไปของเราเองแล้ว! :) ด้วยเหตุนี้ บทเรียนของวันนี้ก็จบลง แต่เราไม่ได้บอกลายาชื่อสามัญ! ในบทเรียนต่อไป เราจะพูดถึงคุณลักษณะขั้นสูงเพิ่มเติม อย่าเพิ่งหายไปไหน! ) เพื่อเสริมสิ่งที่คุณได้เรียนรู้ เราขอแนะนำให้คุณดูบทเรียนวิดีโอจากหลักสูตร Java ของเรา
GO TO FULL VERSION