CodeGym /จาวาบล็อก /สุ่ม /ใช้ varargs เมื่อทำงานกับยาชื่อสามัญ
John Squirrels
ระดับ
San Francisco

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

เผยแพร่ในกลุ่ม
สวัสดี! ในบทเรียนวันนี้ เราจะมาศึกษาเรื่องทั่วไปกันต่อไป เมื่อมันเกิดขึ้น นี่เป็นหัวข้อใหญ่ แต่ก็ไม่มีทางหลีกเลี่ยงได้ — มันเป็นส่วนสำคัญอย่างยิ่งของภาษา :) เมื่อคุณศึกษาเอกสาร Oracle เกี่ยวกับข้อมูลทั่วไปหรืออ่านบทช่วยสอนออนไลน์ คุณจะพบกับคำศัพท์ประเภทที่ไม่สามารถแก้ไขซ้ำได้และประเภทที่สามารถแก้ไขได้ ประเภทที่สามารถแก้ไขได้คือประเภทที่ข้อมูลพร้อมใช้งานอย่างสมบูรณ์ ณ รันไทม์ ใน 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>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
เมธอดaddAll()ใช้เป็นอินพุต a List<E>และวัตถุจำนวนเท่าใดก็ได้Eจากนั้นจึงเพิ่มวัตถุเหล่านี้ทั้งหมดลงในรายการ ในmain()เมธอด เราเรียกaddAll()เมธอดของเราสองครั้ง ในกรณีแรก เราเพิ่มสตริงธรรมดาสองสตริงในList. ทุกอย่างเป็นไปตามลำดับที่นี่ ในกรณีที่สอง เราเพิ่มสองPair<String, String>อ็อบเจกต์ในList. แต่ที่นี่เราได้รับคำเตือนโดยไม่คาดคิด:

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)
นั่นคือ ใน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>) จะถูกลบ ไม่อนุญาตให้สร้างอาร์เรย์ประเภทที่ไม่สามารถแก้ไขซ้ำได้ใน Java คุณสามารถดูสิ่งนี้ได้หากคุณพยายามสร้างอาร์เรย์ Pair<String, String> ด้วยตนเอง

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
เหตุผลชัดเจน: ประเภทความปลอดภัย อย่างที่คุณคงจำได้ เมื่อสร้างอาร์เรย์ คุณต้องระบุวัตถุ (หรือวัตถุดั้งเดิม) ที่อาร์เรย์จะจัดเก็บอย่างแน่นอน

int array[] = new int[10];
ในบทเรียนก่อนหน้านี้ เราได้ตรวจสอบการลบประเภทโดยละเอียด ในกรณีนี้ การลบประเภททำให้เราสูญเสียข้อมูลที่Pairออบเจกต์จัดเก็บไว้<String, String>คู่กัน การสร้างอาร์เรย์จะไม่ปลอดภัย เมื่อใช้วิธีการที่เกี่ยวข้องกับvarargsและ generics อย่าลืมจำเกี่ยวกับการลบประเภทและวิธีการทำงาน หากคุณมั่นใจอย่างแน่นอนเกี่ยวกับโค้ดที่คุณเขียน และรู้ว่าโค้ดจะไม่ก่อให้เกิดปัญหาใดๆ คุณสามารถปิดคำเตือนที่เกี่ยวข้องกับ varargs ได้โดยใช้คำอธิบาย@SafeVarargsประกอบ

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
หากคุณเพิ่มคำอธิบายประกอบนี้ในวิธีการของคุณ คำเตือนที่เราพบก่อนหน้านี้จะไม่ปรากฏขึ้น อีกปัญหาหนึ่งที่สามารถเกิดขึ้นได้เมื่อใช้ varargs ร่วมกับยาชื่อสามัญคือมลพิษแบบกอง การใช้ varargs เมื่อทำงานกับยาชื่อสามัญ - 3มลพิษจากกองขยะสามารถเกิดขึ้นได้ในสถานการณ์ต่อไปนี้:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       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 = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
เอาต์พุตคอนโซล:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
พูดง่ายๆ ก็คือ มลพิษแบบฮีปคือเมื่อวัตถุประเภทAควรอยู่ในฮีป แต่วัตถุประเภทนั้นBจบลงที่นั่นเนื่องจากข้อผิดพลาดที่เกี่ยวข้องกับความปลอดภัยของประเภท ในตัวอย่างของเรา นี่คือสิ่งที่เกิดขึ้นอย่างแน่นอน ขั้นแรก เราสร้างnumbersตัวแปรดิบและกำหนดคอลเล็กชันทั่วไป ( ArrayList<Number>) ให้กับตัวแปรนั้น จากนั้นเราเพิ่มหมายเลขลง1ในคอลเล็กชัน

List<String> strings = numbers;
ในบรรทัดนี้ คอมไพเลอร์พยายามเตือนเราถึงข้อผิดพลาดที่อาจเกิดขึ้นโดยออกคำเตือน " Uncheckedassignment... " แต่เราเพิกเฉย เราลงเอยด้วยตัวแปรประเภททั่วไปList<String>ที่ชี้ไปที่คอลเล็กชันประเภทArrayList<Number>ทั่วไป เห็นได้ชัดว่าสถานการณ์นี้อาจทำให้เกิดปัญหาได้! และมันก็เป็นเช่นนั้น ด้วยการใช้ตัวแปรใหม่ของเรา เราเพิ่มสตริงลงในคอลเลกชัน ขณะนี้เรามีมลภาวะเป็นกอง — เราเพิ่มตัวเลขและสตริงลงในคอลเล็กชันแบบพาราเมตริก คอมไพเลอร์เตือนเรา แต่เราเพิกเฉยต่อคำเตือนของมัน เป็นผลให้เราได้รับClassCastExceptionเฉพาะในขณะที่โปรแกรมกำลังทำงาน แล้วมันเกี่ยวอะไรกับ varargs? การใช้ varargs กับยาชื่อสามัญสามารถนำไปสู่มลพิษแบบกองได้ง่าย นี่คือตัวอย่างง่ายๆ:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(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");

       polluteHeap(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