Java SafeVarargs คำอธิบายประกอบมีมาตรฐานหรือแนวปฏิบัติที่ดีที่สุดหรือไม่?


175

ฉันเพิ่งเจอ@SafeVarargsคำอธิบายประกอบjava Googling สำหรับสิ่งที่ทำให้ฟังก์ชั่น Variadic ใน Java ไม่ปลอดภัยทำให้ฉันค่อนข้างสับสน (heap poisoning? erased types?) ดังนั้นฉันอยากรู้บางสิ่ง:

  1. สิ่งที่ทำให้ฟังก์ชั่น Java variadic ไม่ปลอดภัยใน@SafeVarargsแง่ (อธิบายโดยเฉพาะอย่างยิ่งในรูปแบบของตัวอย่างในเชิงลึก)?

  2. ทำไมคำอธิบายประกอบนี้ถึงดุลยพินิจของโปรแกรมเมอร์? นี่ไม่ใช่สิ่งที่คอมไพเลอร์ควรตรวจสอบใช่ไหม

  3. มีมาตรฐานบางอย่างที่ต้องปฏิบัติตามเพื่อให้แน่ใจว่าการทำงานของเขาปลอดภัยแน่นอน varags? หากไม่เป็นเช่นนั้นวิธีปฏิบัติที่ดีที่สุดที่จะทำให้แน่ใจได้คืออะไร?


1
คุณเคยเห็นตัวอย่าง (และคำอธิบาย) ในJavaDocหรือไม่
jlordo

2
สำหรับคำถามที่สามของคุณหนึ่งคือการปฏิบัติมักจะมีองค์ประกอบแรกและอื่น ๆretType myMethod(Arg first, Arg... others): หากคุณไม่ได้ระบุfirstจะอนุญาตให้ใช้อาร์เรย์ว่างเปล่าได้และคุณอาจมีวิธีการในชื่อเดียวกันโดยที่ชนิดการส่งคืนเดียวกันโดยไม่มีอาร์กิวเมนต์ซึ่งหมายความว่า JVM จะมีเวลาที่ยากลำบากในการพิจารณาว่าควรเรียกวิธีการใด
fge

4
@ jlordo ฉันทำ แต่ฉันไม่เข้าใจว่าทำไมให้ในบริบท varargs เป็นหนึ่งสามารถ replecate สถานการณ์นี้นอกฟังก์ชั่น varargs (ตรวจสอบสิ่งนี้ได้รวบรวมกับคำเตือนความปลอดภัยประเภทที่คาดหวังและข้อผิดพลาดใน runtime) ..
Oren

คำตอบ:


244

1) มีตัวอย่างมากมายบนอินเทอร์เน็ตและใน StackOverflow เกี่ยวกับปัญหาเฉพาะกับ generics และ varargs โดยพื้นฐานแล้วเมื่อคุณมีอาร์กิวเมนต์จำนวนตัวแปรประเภทพารามิเตอร์:

<T> void foo(T... args);

ใน Java, varargs เป็นน้ำตาล syntactic ที่ผ่านการ "เขียนใหม่" อย่างง่ายที่รวบรวมเวลา: พารามิเตอร์ varargs ประเภทX...ถูกแปลงเป็นพารามิเตอร์ประเภทX[]; และเวลาที่สายทำนี้ varargs ทุกวิธีคอมไพเลอร์จะเก็บรวบรวมทั้งหมดของ "ข้อโต้แย้งตัวแปร" ที่จะไปใน varargs new X[] { ...(arguments go here)... }พารามิเตอร์และสร้างอาร์เรย์เช่นเดียวกับ

นี้ทำงานได้ดีเมื่อ varargs String...ประเภทเป็นรูปธรรมเช่น เมื่อมันเป็นตัวแปรประเภทT...มันก็ยังทำงานเมื่อTเป็นที่รู้จักกันเป็นประเภทที่เป็นรูปธรรมสำหรับการโทรนั้น เช่นถ้าวิธีการข้างต้นเป็นส่วนหนึ่งของชั้นเรียนFoo<T>และคุณมีการFoo<String>อ้างอิงแล้วการเรียกfooมันจะไม่เป็นไรเพราะเรารู้ว่าTอยู่Stringที่จุดนั้นในรหัส

อย่างไรก็ตามมันจะไม่ทำงานเมื่อ "value" ของTเป็นพารามิเตอร์ประเภทอื่น ใน Java มันเป็นไปไม่ได้ที่จะสร้างอาร์เรย์ของประเภทองค์ประกอบพารามิเตอร์ ( new T[] { ... }) ดังนั้น Java ใช้แทนnew Object[] { ... }(นี่ObjectคือขอบเขตสูงสุดของT; หากมีขอบเขตบนมีบางอย่างที่แตกต่างกันก็จะเป็นที่แทนที่จะObject) และจากนั้นให้คำเตือนคอมไพเลอร์

ดังนั้นจะเกิดอะไรขึ้นกับการสร้างnew Object[]แทนที่จะเป็นnew T[]หรืออะไรก็ตาม ดีอาร์เรย์ใน Java รู้ว่าประเภทองค์ประกอบของพวกเขาที่รันไทม์ ดังนั้นวัตถุอาร์เรย์ที่ส่งผ่านจะมีประเภทองค์ประกอบที่ไม่ถูกต้องในขณะทำงาน

สำหรับการใช้งาน varargs ที่พบได้บ่อยที่สุดเพียงแค่วนซ้ำองค์ประกอบต่างๆนี่ก็ไม่มีปัญหา (คุณไม่สนใจเกี่ยวกับประเภทรันไทม์ของอาร์เรย์) ดังนั้นสิ่งนี้จึงปลอดภัย:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

อย่างไรก็ตามสำหรับทุกสิ่งที่ขึ้นอยู่กับประเภทองค์ประกอบรันไทม์ของอาร์เรย์ที่ส่งผ่านจะไม่ปลอดภัย นี่คือตัวอย่างง่ายๆของสิ่งที่ไม่ปลอดภัยและล้มเหลว:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

นี่คือปัญหาที่เราขึ้นอยู่กับชนิดของการargsที่จะเป็นเพื่อที่จะกลับเป็นT[] แต่จริงๆแล้วประเภทของการโต้แย้งที่รันไทม์ที่ไม่ได้เป็นตัวอย่างของT[]T[]

3) หากวิธีการของคุณมีอาร์กิวเมนต์เป็นประเภทT...(โดยที่ T เป็นพารามิเตอร์ประเภทใด ๆ ) ดังนั้น:

  • ปลอดภัย: หากวิธีการของคุณขึ้นอยู่กับความจริงที่ว่าองค์ประกอบของอาร์เรย์เป็นอินสแตนซ์ของ T
  • ไม่ปลอดภัย: ถ้ามันขึ้นอยู่กับความจริงที่ว่าอาร์เรย์เป็นตัวอย่างของ T[]

สิ่งที่ขึ้นอยู่กับชนิดรันไทม์ของอาเรย์รวมถึง: คืนมันเป็นประเภทT[]ผ่านมันเป็นข้อโต้แย้งไปยังพารามิเตอร์ประเภทT[]รับประเภทอาร์เรย์โดยใช้.getClass()ผ่านไปยังวิธีการที่ขึ้นอยู่กับประเภทรันไทม์ของอาเรย์เช่นList.toArray()และArrays.copyOf()ฯลฯ

2) ความแตกต่างที่ฉันกล่าวถึงข้างต้นซับซ้อนเกินไปที่จะแยกแยะได้ง่ายโดยอัตโนมัติ


7
ตอนนี้มันก็สมเหตุสมผลแล้ว ขอบคุณ ดังนั้นเพียงเพื่อดูว่าฉันเข้าใจคุณอย่างสมบูรณ์สมมติว่าเรามีชั้นเรียนFOO<T extends Something>มันจะปลอดภัยที่จะกลับ T [] ของวิธีการ varargs เป็นอาร์เรย์ของSomthing?
Oren

30
อาจจะคุ้มค่าที่จะสังเกตว่ากรณีหนึ่งที่มันถูกต้อง (น่าจะ) เสมอที่จะใช้@SafeVarargsคือเมื่อสิ่งเดียวที่คุณทำกับอาเรย์นั้นผ่านไปยังวิธีอื่นที่มีคำอธิบายประกอบอยู่แล้ว (ตัวอย่างเช่น: บ่อยครั้งฉันพบว่า มีอยู่ในการแปลงอาร์กิวเมนต์ของพวกเขาเป็นรายการโดยใช้Arrays.asList(...)และส่งต่อไปยังวิธีอื่นเคสสามารถทำหมายเหตุประกอบได้เสมอ@SafeVarargsเพราะArrays.asListมีหมายเหตุประกอบ)
จูลส์

3
@newacct ฉันไม่ทราบว่าเป็นกรณีนี้ใน Java 7 แต่ Java 8 ไม่อนุญาต@SafeVarargsเว้นแต่ว่าเป็นวิธีการstaticหรือfinalเพราะมิฉะนั้นมันอาจถูกแทนที่ในชั้นเรียนที่ได้มาด้วยบางสิ่งที่ไม่ปลอดภัย
Andy

3
และใน Java 9 จะอนุญาตให้ใช้privateวิธีที่ไม่สามารถเขียนทับได้
Dave L.

4

สำหรับแนวทางปฏิบัติที่ดีที่สุดให้พิจารณาสิ่งนี้

หากคุณมีสิ่งนี้:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

เปลี่ยนเป็น:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

ฉันพบว่าฉันมักจะเพิ่ม varargs เท่านั้นเพื่อให้สะดวกสำหรับผู้โทรของฉัน List<>มันเกือบจะเสมอจะสะดวกมากขึ้นสำหรับการดำเนินงานภายในของฉันที่จะใช้ ดังนั้นเพื่อ piggy-back on Arrays.asList()และให้แน่ใจว่าไม่มีทางที่ฉันสามารถแนะนำ Heap Pollution นี่คือสิ่งที่ฉันทำ

ฉันรู้ว่านี่เป็นเพียงคำตอบที่ # 3 ของคุณ newacct ได้ให้คำตอบที่ดีสำหรับ # 1 และ # 2 ข้างต้นและฉันมีชื่อเสียงไม่มากพอที่จะแสดงความคิดเห็น : P

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