มลพิษที่เป็นไปได้ของฮีปที่เป็นไปได้ผ่านพารามิเตอร์ varargs


433

ฉันเข้าใจว่าสิ่งนี้เกิดขึ้นกับ Java 7 เมื่อใช้ varargs กับประเภททั่วไป

แต่คำถามของฉันคือ ..

Eclipse หมายความว่าอย่างไรเมื่อกล่าวว่า "การใช้งานนั้นอาจก่อให้เกิดมลพิษกับฮีปได้"

และ

@SafeVarargsคำอธิบายประกอบใหม่จะป้องกันสิ่งนี้ได้อย่างไร


8
รายละเอียดที่นี่: docs.oracle.com/javase/specs/jls/se7/html/…
assylias


ฉันเห็นสิ่งนี้ในตัวแก้ไขของฉัน:Possible heap pollution from parameterized vararg type
Alexander Mills

คำตอบ:


252

กองมลพิษเป็นศัพท์เทคนิค มันหมายถึงการอ้างอิงที่มีประเภทที่ไม่ใช่ประเภทของวัตถุที่พวกเขาชี้ไป

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

นี้สามารถนำไปสู่การ "อธิบายไม่ได้" ClassCastExceptions

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargsไม่ได้ป้องกันสิ่งนี้เลย อย่างไรก็ตามมีวิธีการที่พิสูจน์ได้ว่าจะไม่สร้างมลพิษให้กับกองนั้นคอมไพเลอร์ก็ไม่สามารถพิสูจน์ได้ ก่อนหน้านี้ผู้โทรของ API ดังกล่าวจะได้รับคำเตือนที่น่ารำคาญซึ่งไม่มีจุดหมายอย่างสมบูรณ์ แต่ต้องถูกระงับในทุกไซต์การโทร ตอนนี้ผู้เขียน API สามารถระงับได้ครั้งเดียวที่ไซต์การประกาศ

อย่างไรก็ตามหากวิธีการจริงไม่ปลอดภัยผู้ใช้จะไม่ถูกเตือนอีกต่อไป


2
ดังนั้นเราจะพูดว่ากองนั้นมีมลพิษเนื่องจากมีการอ้างอิงที่มีประเภทไม่ใช่สิ่งที่เราอาจคาดหวัง (รายการ <A> เทียบกับรายการ <B> ในตัวอย่างของคุณ)
hertzsprung


30
คำตอบนี้เป็นคำอธิบายที่ดีว่ากองมลพิษคืออะไร แต่ก็ไม่ได้อธิบายว่าทำไม varargs จึงมีความเป็นไปได้สูงที่จะทำให้เกิดการเตือนเฉพาะ
Dolda2000

4
ฉันด้วยฉันไม่มีข้อมูลวิธีการตรวจสอบให้แน่ใจว่ารหัสของฉันไม่มีปัญหานี้ (เช่นฉันจะรู้ได้อย่างไรว่ามันแข็งพอที่จะเพิ่ม @SafeVarargs)
Daniel Alder

237

เมื่อคุณประกาศ

public static <T> void foo(List<T>... bar) คอมไพเลอร์แปลงเป็น

public static <T> void foo(List<T>[] bar) แล้วถึง

public static void foo(List[] bar)

อันตรายนั้นเกิดขึ้นว่าคุณจะกำหนดค่าที่ไม่ถูกต้องผิดพลาดลงในรายการและคอมไพเลอร์จะไม่ทำให้เกิดข้อผิดพลาดใด ๆ ตัวอย่างเช่นถ้าTเป็นStringรหัสต่อไปนี้จะรวบรวมโดยไม่มีข้อผิดพลาด แต่จะล้มเหลวในขณะทำงาน:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

หากคุณตรวจสอบวิธีการเพื่อให้แน่ใจว่าไม่มีช่องโหว่ดังกล่าวคุณสามารถเพิ่มความคิดเห็นด้วย@SafeVarargsเพื่อระงับคำเตือน @SuppressWarnings("unchecked")สำหรับการเชื่อมต่อการใช้งาน

หากคุณได้รับข้อความแสดงข้อผิดพลาดนี้:

วิธีการ Varargs อาจทำให้เกิดมลพิษฮีปจากพารามิเตอร์ varargs ที่ไม่สามารถเรียกคืนได้

และคุณมั่นใจว่าการใช้งานของคุณปลอดภัยแล้วคุณควรใช้@SuppressWarnings("varargs")แทน ดู@SafeVarargs คำอธิบายประกอบที่เหมาะสมสำหรับวิธีนี้หรือไม่ และhttps://stackoverflow.com/a/14252221/14731สำหรับคำอธิบายที่ดีเกี่ยวกับข้อผิดพลาดประเภทที่สองนี้

อ้างอิง:


2
ฉันคิดว่าฉันเข้าใจดีกว่า อันตรายมาเมื่อคุณโยน varargs Object[]ไป ตราบใดที่คุณไม่ได้ทำObject[]มันดูเหมือนว่าคุณควรจะสบายดี
djeikyb

3
ตัวอย่างของสิ่งโง่ ๆ ที่คุณสามารถทำได้: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. bar(Arrays.asList(1,2));แล้วโทร
djeikyb

1
@djeikyb หากอันตรายเกิดขึ้นเฉพาะในกรณีที่ฉันโยนไปObject[]ทำไมผู้แปลจะเรียกคำเตือนถ้าฉันไม่? ควรจะค่อนข้างง่ายต่อการตรวจสอบนี้ในเวลารวบรวมหลังจากทั้งหมด (ในกรณีที่ฉันไม่ผ่านมันไปยังฟังก์ชั่นอื่นที่มีลายเซ็นคล้ายกันในกรณีนี้ฟังก์ชั่นอื่น ๆ ควรเรียกเตือน) ฉันไม่เชื่อว่านี่เป็นหัวใจหลักของการเตือน ("คุณปลอดภัยหากคุณไม่ได้แสดง") และฉันก็ยังไม่เข้าใจในกรณีที่ฉันสบายดี
คำถามที่ 3

5
@djeikyb คุณอาจทำสิ่งที่โง่เหมือนกันโดยไม่ต้อง varargs parametrized (เช่นbar(Integer...args)) แล้วประเด็นของการเตือนนี้คืออะไร?
Vasiliy Vlasov

3
@VasiliyVlasov ปัญหานี้เกี่ยวข้องกับ varargs ที่กำหนดพารามิเตอร์เท่านั้น หากคุณพยายามทำสิ่งเดียวกันกับอาร์เรย์ที่ไม่พิมพ์ runtime จะป้องกันคุณจากการแทรกชนิดที่ไม่ถูกต้องลงในอาร์เรย์ คอมไพเลอร์เตือนคุณว่ารันไทม์จะไม่สามารถป้องกันพฤติกรรมที่ไม่ถูกต้องได้เนื่องจากไม่รู้จักชนิดของพารามิเตอร์ที่รันไทม์ (ตรงกันข้ามคอนทราสต์จะทราบชนิดขององค์ประกอบที่ไม่ใช่ทั่วไปเมื่อรันไทม์)
Gili

8

@SafeVarargs ไม่ได้ป้องกันไม่ให้เกิดขึ้นอย่างไรก็ตามมันสั่งว่าคอมไพเลอร์เข้มงวดเมื่อรวบรวมรหัสที่ใช้

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.htmlอธิบายสิ่งนี้ในรายละเอียดเพิ่มเติม

มลพิษกองคือเมื่อคุณได้รับClassCastExceptionเมื่อทำการดำเนินการบนอินเทอร์เฟซทั่วไปและประกอบด้วยชนิดอื่นกว่าที่ประกาศ


ข้อ จำกัด ของคอมไพเลอร์เพิ่มเติมในการใช้งานดูเหมือนจะไม่เกี่ยวข้องโดยเฉพาะ
พอล Bellora

6

เมื่อคุณใช้ varargs มันอาจส่งผลในการสร้างการที่Object[]จะถืออาร์กิวเมนต์

เนื่องจากการวิเคราะห์แบบ escape, JIT สามารถปรับการสร้างอาร์เรย์นี้ให้เหมาะสม (หนึ่งในไม่กี่ครั้งที่ฉันพบว่ามันทำเช่นนั้น) มันไม่รับประกันว่าจะได้รับการปรับให้เหมาะที่สุด แต่ฉันจะไม่กังวลเกี่ยวกับมันจนกว่าคุณจะเห็นปัญหาใน profiler หน่วยความจำของคุณ

AFAIK @SafeVarargsไม่แสดงคำเตือนของคอมไพเลอร์และจะไม่เปลี่ยนวิธีการทำงานของ JIT


6
@SafeVarargsที่น่าสนใจแม้ว่ามันจะไม่ได้โดดตอบคำถามของเขาเกี่ยวกับ
พอล Bellora

1
Nope นั่นไม่ใช่สิ่งที่กองมลพิษ "มลภาวะฮีปเกิดขึ้นเมื่อตัวแปรชนิดที่กำหนดพารามิเตอร์อ้างถึงวัตถุที่ไม่ใช่ประเภทพารามิเตอร์นั้น" Ref: docs.oracle.com/javase/tutorial/java/generics/ …
Doradus

1

เหตุผลเป็นเพราะ varargs ให้ตัวเลือกในการถูกเรียกด้วยอาร์เรย์วัตถุที่ไม่ใช่พารามิเตอร์ ดังนั้นหากประเภทของคุณคือรายการ <A> ... ก็สามารถเรียกได้ด้วยประเภท [] ไม่ใช่ประเภท varargs

นี่คือตัวอย่าง:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

ตามที่คุณเห็นรายการ [] b สามารถมีคอนซูเมอร์ประเภทใดก็ได้และรหัสนี้ก็คอมไพล์ ถ้าคุณใช้ varargs คุณก็ใช้ได้ แต่ถ้าคุณใช้นิยามเมธอดหลังการลบประเภท - การทดสอบโมฆะ (รายการ []) - คอมไพเลอร์จะไม่ตรวจสอบประเภทพารามิเตอร์เทมเพลต @SafeVarargs จะระงับคำเตือนนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.