JavaRush /จาวาบล็อก /Random-TH /การใช้ varargs เมื่อทำงานกับยาชื่อสามัญ

การใช้ varargs เมื่อทำงานกับยาชื่อสามัญ

เผยแพร่ในกลุ่ม
สวัสดี! ในบทเรียนวันนี้ เราจะศึกษาเรื่องยาชื่อสามัญต่อไป มันบังเอิญว่านี่เป็นหัวข้อใหญ่ แต่ไม่มีที่ไหนให้ไป - นี่เป็นส่วนที่สำคัญอย่างยิ่งของภาษา :) เมื่อคุณศึกษาเอกสารของ Oracle เกี่ยวกับยาสามัญหรืออ่านคำแนะนำบนอินเทอร์เน็ตคุณจะพบกับข้อกำหนดประเภทที่ไม่สามารถเติมได้และประเภทที่เติมได้ คำว่า “Reifiable” คืออะไร? แม้ว่าทุกอย่างจะดีกับภาษาอังกฤษ แต่คุณไม่น่าจะเจอมัน มาลองแปลกัน! การใช้ varargs เมื่อทำงานกับยาชื่อสามัญ - 2
*ขอบคุณ 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>Emain()addAll()ListListPair<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ยาสามัญร่วมกันคือมลภาวะแบบฮีป การใช้ varargs เมื่อทำงานกับยาชื่อสามัญ - 4การปนเปื้อนสามารถเกิดขึ้นได้ในสถานการณ์ต่อไปนี้:
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 ” จะไม่อ่านเอง! :) พบกันใหม่!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION