สวัสดี! วันนี้เราจะพูดถึงยาชื่อสามัญ ต้องบอกว่าคุณจะได้เรียนรู้สิ่งใหม่ ๆ มากมาย! ไม่เพียงเท่านี้ แต่ยังจะมีการบรรยายเรื่องยาชื่อสามัญในครั้งต่อไปด้วย ดังนั้นหากหัวข้อนี้น่าสนใจสำหรับคุณ แสดงว่าคุณโชคดี วันนี้คุณจะได้เรียนรู้มากมายเกี่ยวกับคุณสมบัติของยาชื่อสามัญ ถ้าไม่ก็ใจเย็น ๆ และผ่อนคลาย! :) นี่เป็นหัวข้อที่สำคัญมากและคุณจำเป็นต้องรู้ มาเริ่มกันด้วยคำถามง่ายๆ: "อะไร" และ "ทำไม" ยาชื่อสามัญคืออะไร? ข้อมูลทั่วไปคือประเภทที่มีพารามิเตอร์ เมื่อสร้างข้อมูลทั่วไป คุณไม่เพียงแต่ระบุประเภทของข้อมูลเท่านั้น แต่ยังรวมถึงประเภทของข้อมูลที่ควรใช้ด้วย ฉันคิดว่าตัวอย่างที่ชัดเจนที่สุดอยู่ในใจคุณแล้ว - นี่คือ 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");
}
}
ดังที่คุณอาจเดาได้ ลักษณะเฉพาะของรายการก็คือ ไม่สามารถ "ยัด" ทุกอย่างลงไปได้: มันใช้งานได้กับวัตถุString
เท่านั้น ตอนนี้เรามาดูประวัติศาสตร์ของ 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;
}
}
สมมติว่าเราต้องการให้รายการของเราเก็บเฉพาะInteger
ตัวเลข เราไม่มียาชื่อสามัญ เราไม่สามารถระบุอินสแตนซ์ o ของการตรวจสอบInteger
ในไฟล์add()
. จากนั้นทั้งชั้นเรียนของเราจะเหมาะสำหรับเท่านั้นInteger
และเราจะต้องเขียนคลาสเดียวกันสำหรับประเภทข้อมูลทั้งหมดที่มีอยู่ในโลก! เราตัดสินใจที่จะพึ่งพาโปรแกรมเมอร์ของเราและเพียงแสดงความคิดเห็นในโค้ดเพื่อที่พวกเขาจะได้ไม่ต้องเพิ่มสิ่งที่ไม่จำเป็นลงไป:
//use it ONLY with 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 ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: java.lang.String ไม่สามารถส่งไปที่ java.lang.Integer ที่ Main.main(Main.java:14) สิ่งที่แย่ที่สุดในสถานการณ์นี้คืออะไร ห่างไกลจากการไม่ตั้งใจของโปรแกรมเมอร์ สิ่งที่แย่ที่สุดคือรหัสผิดไปอยู่ในตำแหน่งสำคัญในโปรแกรมของเราและคอมไพล์ได้สำเร็จ ตอนนี้เราจะเห็นข้อผิดพลาดไม่ใช่ในขั้นตอนการเขียนโค้ด แต่เฉพาะในขั้นตอนการทดสอบเท่านั้น (และนี่คือกรณีที่ดีที่สุด!) การแก้ไขข้อบกพร่องในภายหลังในการพัฒนามีค่าใช้จ่ายมากขึ้นทั้งเงินและเวลา นี่เป็นข้อได้เปรียบของยาชื่อสามัญ: คลาสทั่วไปจะช่วยให้โปรแกรมเมอร์ที่โชคร้ายตรวจพบข้อผิดพลาดได้ทันที รหัสจะไม่รวบรวม!
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 ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: java.lang.String ไม่สามารถส่งไปที่ java.lang.Integer ที่ Main.main(Main.java:16) นั่นคือ แม้จะใช้เครื่องมือ "native" Java คุณสามารถทำผิดพลาดและสร้างคอลเลกชันที่ไม่ปลอดภัยได้ อย่างไรก็ตาม หากเราวางโค้ดนี้ลงใน IDEa เราจะเห็นคำเตือน: “ Unchecked call to add(E) as a member of raw type of java.util.List ” มันบอกเราว่ามีบางอย่างอาจผิดพลาดเมื่อเพิ่มองค์ประกอบลงใน คอลเลกชันที่ไม่มียาชื่อสามัญไม่ใช่วิธีนี้ แต่วลี "ประเภทดิบ" หมายถึงอะไร? การแปลตามตัวอักษรจะค่อนข้างแม่นยำ - " ประเภทดิบ " หรือ " ประเภทสกปรก " Raw type
เป็นคลาสทั่วไปที่ถูกลบประเภทออกไป กล่าวอีกนัยหนึ่งList myList1
นี่คือRaw type
. สิ่งที่ตรงกันข้ามraw type
คือgeneric type
คลาสทั่วไป (หรือที่เรียกว่าคลาสparameterized type
) ที่สร้างขึ้นอย่างถูกต้องพร้อมข้อกำหนดประเภท ตัวอย่างเช่น, List<String> myList1
. คุณอาจมีคำถาม: เหตุใดจึงได้รับอนุญาตให้ใช้raw types
? เหตุผลง่ายๆ ผู้สร้าง Java ทิ้งการสนับสนุนในภาษาraw types
ไว้เพื่อไม่ให้เกิดปัญหาความเข้ากันได้ เมื่อถึงเวลาที่ Java 5.0 เปิดตัว (ข้อมูลทั่วไปปรากฏขึ้นเป็นครั้งแรกในเวอร์ชันนี้) มีการเขียนโค้ดจำนวนมากโดยใช้raw types
. ดังนั้นความเป็นไปได้นี้ยังคงมีอยู่จนถึงทุกวันนี้ เราได้กล่าวถึงหนังสือคลาสสิกของ Joshua Bloch เรื่อง “Effective Java” มากกว่าหนึ่งครั้งในการบรรยาย ในฐานะหนึ่งในผู้สร้างภาษา เขาไม่ได้ละเลยหัวข้อการใช้raw types
และ ใน generic types
หนังสือ บทที่ 23 ของหนังสือเล่มนี้มีชื่อที่ไพเราะมาก: “อย่าใช้ชนิดดิบในโค้ดใหม่” นี่คือสิ่งที่คุณต้องจำ เมื่อใช้คลาสทั่วไป ห้ามแปลงคลาสเหล่านั้นgeneric type
เป็นraw type
.
วิธีการพิมพ์
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("Старая строка 1");
strings.add("Старая строка 2");
strings.add("Старая строка 3");
fill(strings, "Новая строка");
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()
ทำงานกับข้อมูลประเภทต่างๆ ได้อย่างง่ายดาย ขั้นแรก จะต้องป้อนรายการสตริงและสตริง จากนั้นป้อนรายการตัวเลขและตัวเลข เอาต์พุตคอนโซล: [Newline, Newline, Newline] [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("Старая строка");
System.out.println(stringBox.get());
stringBox.set("Новая строка");
System.out.println(stringBox.get());
stringBox.set(12345);//ошибка компиляции!
}
}
Box<T>
พิมพ์ ชั้นเรียนของเรา (“กล่อง”) หลังจากกำหนดประเภทข้อมูล ( ) ให้กับมันในระหว่างการสร้าง<T>
เราจะไม่สามารถวางวัตถุประเภทอื่นลงไปได้อีกต่อไป นี้สามารถเห็นได้ในตัวอย่าง เมื่อสร้าง เราระบุว่าวัตถุของเราจะทำงานกับสตริง:
Box<String> stringBox = new Box<>();
และเมื่อในบรรทัดสุดท้ายของโค้ดที่เราพยายามใส่หมายเลข 12345 ลงในช่อง เราได้รับข้อผิดพลาดในการคอมไพล์! เพียงเท่านี้ เราก็สร้างคลาสทั่วไปของเราเองขึ้นมา! :) นี่เป็นการสรุปการบรรยายของเราในวันนี้ แต่เราไม่ได้บอกลายาสามัญ! ในการบรรยายครั้งต่อไป เราจะพูดถึงคุณสมบัติขั้นสูงเพิ่มเติม ดังนั้นอย่าเพิ่งบอกลา! ) ขอให้โชคดีในการศึกษาของคุณ! :)
GO TO FULL VERSION