สวัสดี! ในบทเรียนวันนี้ เราจะศึกษาเรื่องยาชื่อสามัญต่อไป มันบังเอิญว่านี่เป็นหัวข้อใหญ่ แต่ไม่มีที่ไหนให้ไป - นี่เป็นส่วนที่สำคัญอย่างยิ่งของภาษา :) เมื่อคุณศึกษาเอกสารของ Oracle เกี่ยวกับยาสามัญหรืออ่านคำแนะนำบนอินเทอร์เน็ตคุณจะพบกับข้อกำหนดประเภทที่ไม่สามารถเติมได้และประเภทที่เติมได้ คำว่า “Reifiable” คืออะไร? แม้ว่าทุกอย่างจะดีกับภาษาอังกฤษ แต่คุณไม่น่าจะเจอมัน มาลองแปลกัน!
*ขอบคุณ Google คุณช่วยได้มาก -_-*
ประเภทที่ reifiableคือประเภทที่มีข้อมูลครบถ้วน ณ รันไทม์ ในภาษา Java สิ่งเหล่านี้รวมถึงประเภทพื้นฐาน ประเภทดิบ และประเภทที่ไม่ใช่ทั่วไป ในทางตรงกันข้ามประเภทที่ไม่สามารถทำซ้ำได้คือประเภทที่ข้อมูลถูกลบและทำให้ไม่สามารถใช้งานได้ในขณะรันไทม์ สิ่งเหล่านี้เป็นเพียงข้อมูลทั่วไป - List<String> , List<Integer>ฯลฯ
ยังไงก็ตามคุณจำได้ไหมว่าvarargs คืออะไร ?
ในกรณีที่คุณลืม นี่คืออาร์กิวเมนต์ที่มีความยาวผันแปรได้ มันมีประโยชน์ในสถานการณ์ที่เราไม่รู้ว่าสามารถส่งผ่านข้อโต้แย้งไปยังวิธีการของเราได้กี่ข้อ เช่น ถ้าเรามีคลาสเครื่องคิดเลขและมีsum
เมธอด sum()
คุณสามารถส่งผ่าน 2 หมายเลข 3, 5 หรือตัวเลขใดๆไปยังเมธอดได้ มันจะแปลกมากที่จะโอเวอร์โหลดวิธีการทุกครั้งsum()
เพื่อคำนึงถึงตัวเลือกที่เป็นไปได้ทั้งหมด แต่เราสามารถทำได้แทน:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
เอาต์พุตคอนโซล:
15
11
ดังนั้นเมื่อใช้varargs
ร่วมกับยาชื่อสามัญจึงมีคุณสมบัติที่สำคัญบางประการ ลองดูรหัสนี้:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // здесь все нормально
"Leonardo da Vinci",
"Vasco de Gama"
);
// а здесь мы получаем предупреждение
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
วิธีการ รับรายการ และวัตถุจำนวนเท่าใดก็ได้addAll()
เป็นอินพุตจากนั้นจึงเพิ่มวัตถุเหล่านี้ทั้งหมดลงในรายการ ในวิธีการที่เราเรียกวิธีการของเราสองครั้ง ครั้งแรกที่เราเพิ่มสองบรรทัดปกติ ทุกอย่างเรียบร้อยดีที่นี่ ครั้งที่สองเราเพิ่มวัตถุสองชิ้น และทันใดนั้นเราก็ได้รับคำเตือน: List<E>
E
main()
addAll()
List
List
Pair<String, String>
Unchecked generics array creation for varargs parameter
มันหมายความว่าอะไร? เหตุใดเราจึงได้รับคำเตือน และเกี่ยวอะไรกับคำเตือนarray
? Array
- นี่คืออาร์เรย์และไม่มีอาร์เรย์ในโค้ดของเรา! เริ่มจากอันที่สองกันก่อน คำเตือนกล่าวถึงอาร์เรย์เนื่องจากคอมไพลเลอร์แปลงอาร์กิวเมนต์ความยาวผันแปร (varargs) เป็นอาร์เรย์ กล่าวอีกนัยหนึ่ง ลายเซ็นของวิธีการของเราคือaddAll()
:
public static <E> void addAll(List<E> list, E... array)
จริงๆ แล้วดูเหมือนว่านี้:
public static <E> void addAll(List<E> list, E[] array)
นั่นคือใน method main()
คอมไพเลอร์จะเปลี่ยนโค้ดของเราเป็น:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
String
ทุกอย่างเรียบร้อย ดี กับอาร์เรย์ แต่ด้วยอาร์เรย์Pair<String, String>
- ไม่ ความจริงก็คือว่าPair<String, String>
นี่เป็นประเภทที่ไม่สามารถทดแทนได้ ในระหว่างการคอมไพล์ ข้อมูลทั้งหมดเกี่ยวกับประเภทพารามิเตอร์ (<String, String>) จะถูกลบ ไม่อนุญาตให้สร้างอาร์เรย์จาก Non-Reifiable Type ใน Java คุณสามารถตรวจสอบสิ่งนี้ได้หากคุณพยายามสร้างอาร์เรย์คู่<สตริง, สตริง> ด้วยตนเอง
public static void main(String[] args) {
// ошибка компиляции! Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
เหตุผลที่ชัดเจน - ความปลอดภัยประเภท ดังที่คุณจำได้ เมื่อสร้างอาร์เรย์ คุณต้องระบุว่าวัตถุใด (หรือวัตถุพื้นฐาน) อาร์เรย์นี้จะจัดเก็บ
int array[] = new int[10];
ในบทเรียนก่อนหน้าบทหนึ่ง เราได้ตรวจสอบกลไกการลบประเภทอย่างละเอียด ดังนั้นในกรณีนี้ อันเป็นผลมาจากการลบประเภท เราจึงสูญเสียข้อมูลที่Pair
คู่ถูกเก็บไว้ ในวัตถุ <String, String>
ของ เรา การสร้างอาร์เรย์จะไม่ปลอดภัย เมื่อใช้วิธีการที่มีvarargs
และยาชื่อสามัญ อย่าลืมจำการลบประเภทและวิธีการทำงานอย่างชัดเจน หากคุณมั่นใจอย่างยิ่งในโค้ดที่คุณเขียน และรู้ว่าจะไม่ทำให้เกิดปัญหาใดๆ คุณสามารถปิดใช้งานvarargs
คำเตือนที่เกี่ยวข้องได้โดยใช้คำอธิบายประกอบ@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
หากคุณเพิ่มคำอธิบายประกอบนี้ให้กับวิธีการของคุณ คำเตือนที่เราพบก่อนหน้านี้จะไม่ปรากฏขึ้น ปัญหาที่เป็นไปได้อีกประการหนึ่งเมื่อใช้varargs
ยาสามัญร่วมกันคือมลภาวะแบบฮีป การปนเปื้อนสามารถเกิดขึ้นได้ในสถานการณ์ต่อไปนี้:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> makeHeapPollution() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = makeHeapPollution();
System.out.println(stringsWithHeapPollution.get(0));
}
}
เอาต์พุตคอนโซล:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
กล่าวง่ายๆ ก็คือ มลภาวะของฮีปคือสถานการณ์ที่ออบเจ็กต์ประเภท 1 ควรอยู่บนฮีปА
แต่ออบเจ็กต์ประเภทจะจบลงที่นั่นB
เนื่องจากข้อผิดพลาดด้านความปลอดภัยของประเภท ในตัวอย่างของเรา นี่คือสิ่งที่เกิดขึ้น ขั้นแรกเราสร้างตัวแปร Raw numbers
และกำหนดให้เป็นคอลเลกชันArrayList<Number>
ทั่วไป หลังจากนั้นเราก็เพิ่มหมายเลขที่1
นั่น
List<String> strings = numbers;
ในบรรทัดนี้ คอมไพลเลอร์พยายามเตือนเราเกี่ยวกับข้อผิดพลาดที่อาจเกิดขึ้นโดยออกคำเตือน “ การมอบหมายที่ไม่ถูกตรวจสอบ... ” แต่เราเพิกเฉย ด้วยเหตุนี้ เราจึงมีตัวแปรทั่วไป type List<String>
ซึ่งชี้ไปที่คอลเลกชันทั่วไปของArrayList<Number>
type สถานการณ์นี้สามารถนำไปสู่ปัญหาได้อย่างชัดเจน! นี่คือสิ่งที่เกิดขึ้น ด้วยการใช้ตัวแปรใหม่ของเรา เราจะเพิ่มสตริงให้กับคอลเลกชัน ฮีปเสียหาย - เราเพิ่มตัวเลขก่อนแล้วจึงเพิ่มสตริงในคอลเลกชันที่พิมพ์ คอมไพเลอร์เตือนเรา แต่เราเพิกเฉยต่อคำเตือน โดยรับผลลัพธ์ClassCastException
เฉพาะในขณะที่โปรแกรมกำลังทำงานอยู่เท่านั้น มันเกี่ยวอะไรกับมันvarargs
? การใช้varargs
ร่วมกับยาชื่อสามัญสามารถนำไปสู่มลภาวะแบบฮีปได้อย่างง่ายดาย นี่เป็นตัวอย่างง่ายๆ:
import java.util.Arrays;
import java.util.List;
public class Main {
static void makeHeapPollution(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
makeHeapPollution(cars1, cars2);
}
}
เกิดอะไรขึ้นที่นี่? เนื่องจากการลบประเภท แผ่นพารามิเตอร์ของเรา (เราจะเรียกพวกเขาว่า "แผ่นงาน" แทนที่จะเป็น "รายการ" เพื่อความสะดวก) คือ -
List<String>...stringsLists
- จะกลายเป็นอาร์เรย์ของชีต - List[]
ด้วยประเภทที่ไม่รู้จัก (อย่าลืมว่า varargs จะกลายเป็นอาร์เรย์ปกติอันเป็นผลมาจากการคอมไพล์) ด้วยเหตุนี้ เราจึงสามารถกำหนดตัวแปรObject[] array
ในบรรทัดแรกของวิธีการได้อย่างง่ายดาย - ประเภทต่างๆ ได้ถูกลบออกจากชีตของเราแล้ว! และตอนนี้เรามีตัวแปรประเภทObject[]
ที่เราสามารถเพิ่มอะไรก็ได้ - ออบเจ็กต์ทั้งหมดใน Java สืบทอดมาจากObject
! ตอนนี้เรามีเพียงอาร์เรย์ของแผ่นสตริงเท่านั้น แต่ด้วยการใช้varargs
และการลบประเภท เราจึงสามารถเพิ่มแผ่นตัวเลขให้กับพวกมันได้อย่างง่ายดาย ซึ่งเป็นสิ่งที่เราทำ เป็นผลให้เราสร้างมลพิษให้กับฮีปโดยการผสมวัตถุประเภทต่างๆ ผลลัพธ์จะเป็นข้อยกเว้นเดียวกันClassCastException
เมื่อพยายามอ่านสตริงจากอาร์เรย์ เอาต์พุตคอนโซล:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
สิ่งเหล่านี้เป็นผลที่ตามมาอย่างไม่คาดคิดซึ่งอาจเกิดจากการใช้กลไกที่ดูเรียบง่ายvarargs
:) และนี่คือจุดที่การบรรยายของเราในวันนี้สิ้นสุดลง อย่าลืมแก้ปัญหาสองสามข้อ และหากคุณมีเวลาและพลังงานเหลืออยู่ ให้ศึกษาวรรณกรรมเพิ่มเติม “ Effective Java ” จะไม่อ่านเอง! :) พบกันใหม่!
GO TO FULL VERSION