เป็นไปได้หรือไม่ที่จะแก้ปัญหา“ คำสั่งคอมไพเลอร์ทั่วไปของ T ถูกสร้างขึ้นสำหรับพารามิเตอร์ varargs”?


153

นี่เป็นเวอร์ชันที่ง่ายของโค้ดที่เป็นปัญหาคลาสทั่วไปหนึ่งคลาสใช้คลาสอื่นที่มีพารามิเตอร์ชนิดทั่วไปและต้องการส่งหนึ่งประเภททั่วไปไปยังวิธีที่มีพารามิเตอร์ varargs:

class Assembler<X, Y> {
    void assemble(X container, Y... args) { ... }
}

class Component<T> {
    void useAssembler(T something) {

        Assembler<String, T> assembler = new Assembler<String, T>();

        //generates warning:
        // Type safety : A generic array of T is
        // created for a varargs parameter
        assembler.assemble("hello", something);
    }

}

มีวิธีที่ถูกต้องในการส่งต่อพารามิเตอร์ทั่วไปไปยังวิธีการ varargs โดยไม่ต้องพบคำเตือนนี้หรือไม่?

แน่นอนว่าสิ่งที่ชอบ

assembler.assemble("hello", new T[] { something });

ไม่ทำงานเนื่องจากคุณไม่สามารถสร้างอาร์เรย์ทั่วไป


3
หนึ่งที่แปลก ดูเหมือนว่าคอมไพเลอร์จะสามารถรับประกันความปลอดภัยแบบเต็มได้ที่นี่
erickson

3
รายการที่เกี่ยวข้องใน Java Generics ของ Angelika Langer คำถามที่พบบ่อย: angelikalanger.com/GenericsFAQ/FAQSections/ ......
Flow

คำตอบ:


88

นอกเหนือจากการเพิ่ม@SuppressWarnings("unchecked")ฉันไม่คิดอย่างนั้น

รายงานข้อผิดพลาดนี้มีข้อมูลเพิ่มเติม แต่จะเดือดลงไปยังคอมไพเลอร์ที่ไม่ชอบอาร์เรย์ประเภททั่วไป


3
ลืมพูดถึงฉันต้องการหลีกเลี่ยง @SuppressWarnings ("ไม่ถูกตรวจสอบ") รายงานข้อผิดพลาดนั้นให้ความหวังเล็กน้อยกับฉัน!
matt b

3
ดังที่ Joshua Bloch วางไว้ใน Java ที่มีประสิทธิภาพ: "อย่าผสมยาชื่อสามัญและอาร์เรย์"
Timmos

20
ดังนั้นโดยปริยาย: อย่าใช้ Varargs กับ Generics! ถูกต้อง ... การตัดสินใจที่จะแมป varargs กับ Array ไม่ใช่คอลเล็กชันจะทำให้จาวาต่อยตลอดไป นายกอสลิ่งทำดีมาก
bernstein

57

Tom Hawtin ชี้ให้เห็นสิ่งนี้ในความคิดเห็น แต่ชัดเจนยิ่งขึ้น: ใช่คุณสามารถแก้ปัญหานี้ได้ที่ไซต์การประกาศ (แทนที่จะเป็นไซต์การโทร (อาจมีจำนวนมาก)): เปลี่ยนเป็น JDK7

ดังที่คุณเห็นในโพสต์บล็อกของ Joseph Darcy Project Coin แบบฝึกหัดเพื่อเลือกการปรับปรุงภาษาเพิ่มเติมเล็กน้อยสำหรับ Java 7 ยอมรับข้อเสนอของ Bob Leeเพื่อให้สิ่งต่าง ๆ เช่น@SuppressWarnings("varargs")ที่ด้านวิธีที่จะทำให้คำเตือนนี้หายไปในสถานการณ์ที่รู้กันว่าเป็น ปลอดภัย

สิ่งนี้ถูกนำไปใช้ใน OpenJDK ด้วยการกระทำนี้

สิ่งนี้อาจเป็นประโยชน์กับโครงการของคุณหรือไม่ (หลาย ๆ คนคงไม่ยินดีที่จะเปลี่ยนไปใช้รุ่น JVM ที่ไม่เสถียรรุ่นก่อนวางจำหน่าย!) แต่บางทีมันก็เป็น - หรือบางทีคนที่พบคำถามนี้ในภายหลัง (หลังจาก JDK7 หมด ) จะพบว่ามีประโยชน์


7
ฟีเจอร์ Project Project ดังกล่าวมีให้บริการแล้ว - ดู@SafeVarargsใน Java 7
George Hawkins

ทางเลือก E ในข้อเสนอของ Bob กำลังล่อลวง
Christopher Perry

Java 8 ดูเหมือนจะใช้ @SafeVarargs แทน @SuppressWarnings ("varargs")
Paul Wintz

17

หากคุณอยู่หลังอินเตอร์เฟสประเภทคล่องแคล่วคุณสามารถลองรูปแบบการสร้าง ไม่กระชับเท่า varargs แต่มันปลอดภัยสำหรับพิมพ์

วิธีการพิมพ์ทั่วไปแบบคงที่สามารถกำจัดบางส่วนหม้อไอน้ำเมื่อใช้ตัวสร้างในขณะที่รักษาความปลอดภัยของประเภท

ผู้สร้าง

public class ArgBuilder<T> implements Iterable<T> {

    private final List<T> args = new ArrayList<T>();

    public ArgBuilder<T> and(T arg) {
        args.add(arg);
        return this;
    }

    @Override
    public Iterator<T> iterator() {
        return args.iterator();
    }

    public static <T> ArgBuilder<T> with(T firstArgument) {
        return new ArgBuilder<T>().and(firstArgument);
    }
}

ใช้มัน

import static com.example.ArgBuilder.*;

public class VarargsTest {

    public static void main(String[] args) {
        doSomething(new ArgBuilder<String>().and("foo").and("bar").and("baz"));
        // or
        doSomething(with("foo").and("bar").and("baz"));
    }

    static void doSomething(Iterable<String> args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

1
พลังขององค์ประกอบ ฉันชอบสิ่งนี้มากกว่า varargs มันแสดงออกได้มากกว่า
Christopher Perry

1
@ChristopherPerry ดีคุณต้องพิจารณาฐานรหัสของคุณด้วย พื้นฐานCollection(ในกรณีนี้ArrayListคือ) ถูกบังคับให้ผู้เรียกในขณะที่พวกเขาอาจรู้ว่าLinkedListเป็นที่เหมาะสมกว่าหรืออาร์เรย์ที่ไม่เปลี่ยนรูปตัวเอง (เช่น varargs จากคำถาม OP) ในกรณีที่ไม่ได้ใช้งานเฉพาะกรณีนี้อาจเหมาะสม แต่เพียงชี้ให้เห็นว่านี่เป็นข้อ จำกัด ในบางวิธีขึ้นอยู่กับรหัสที่ล้อมรอบสิ่งนี้และความต้องการของคุณ
searchengine27

5

การคัดเลือกพารามิเตอร์อย่างชัดเจนไปยัง Object ในการเรียกใช้เมธอด vararg จะทำให้คอมไพเลอร์มีความสุขโดยไม่ต้องใช้ @SuppressWarnings

public static <T> List<T> list( final T... items )
{
    return Arrays.asList( items );
}

// This will produce a warning.
list( "1", 2, new BigDecimal( "3.5" ) )

// This will not produce a warning.
list( (Object) "1", (Object) 2, (Object) new BigDecimal( "3.5" ) )

// This will not produce a warning either. Casting just the first parameter to 
// Object appears to be sufficient.
list( (Object) "1", 2, new BigDecimal( "3.5" ) )

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

เพื่อให้เข้าใจได้ดียิ่งขึ้นลองเล่นกับการเรียกใช้เมธอดลิสต์ด้านบนเปรียบเทียบกับเมธอด list2 ต่อไปนี้

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}

ใช้งานได้เช่น: Iterator <?> it = Arrays.asList ((Object) t) .iterator; ถ้า (ถ้า, hasNext ()) {class = it.next (). getClass (); } ยกตัวอย่างเพื่อดึงคลาสของออบเจกต์ออกจากอาเรย์ประเภทที่ไม่รู้จัก
ggb667

2

คุณสามารถเพิ่ม@SafeVarargsเป็นวิธีการได้ตั้งแต่ Java 7 และคุณไม่จำเป็นต้องใส่คำอธิบายประกอบบนรหัสลูกค้า

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}

1

คุณสามารถโอเวอร์โหลดเมธอดได้ วิธีนี้ไม่ได้แก้ปัญหาของคุณ แต่จะลดจำนวนคำเตือน (และใช่มันแฮ็ค!)

class Assembler<X, Y> {
  void assemble(X container, Y a1) { ... }
  void assemble(X container, Y a1, Y a2) { ... }
  void assemble(X container, Y a1, Y a2, Y a3) { ... }
  void assemble(X container, Y a1, Y a2, Y a3, Y a4) { ... }
  void assemble(X container, Y... args) { ... }
}

23
Ew นี่คือประเภทของการแฮ็กที่ varargs ควรจะป้องกัน
Amanda S

1
นี้อาจจะเป็นวิธีการที่ถูกต้องตัวอย่างเช่นดูที่ฝรั่งของImmutableSet.of
Jonathan

1

มันเป็นปัญหาง่าย ๆ ในการแก้: ใช้List<T>!

ควรหลีกเลี่ยงอาร์เรย์ประเภทอ้างอิง

ในเวอร์ชันปัจจุบันของ Java (1.7) คุณสามารถทำเครื่องหมายด้วยวิธี@SafeVargsที่จะลบคำเตือนจากผู้โทร โปรดระวังด้วยว่าและคุณยังดีกว่าโดยไม่ต้องใช้อาร์เรย์เดิม

เห็นแล้วยังปรับปรุงคำเตือนคอมไพเลอร์และข้อผิดพลาดเมื่อใช้ไม่ reifiable พารามิเตอร์อย่างเป็นทางการกับ Varargs วิธีการบันทึกเทคโนโลยี


6
สิ่งนี้หลีกเลี่ยงไม่ได้ด้วยพารามิเตอร์ varargs ใช่ไหม
ด้านข

4
มีข้อเสนอสำหรับ JDK7 เพื่อให้การยับยั้งคำเตือนไปที่การประกาศเมธอด varargs แทนการใช้งาน
Tom Hawtin - tackline

11
สิ่งนี้จะละเว้นประเด็นสำคัญของคำถามของผู้เขียนอย่างสิ้นเชิง - พารามิเตอร์ varargs สร้างอาร์เรย์และสร้างคำเตือนนี้
Daniel Yankowsky

2
ฉันเห็นด้วยกับ @Tom Hawtin - tackline สำหรับรายละเอียดโปรดดู Bloch << Effecive Java >> รายการ 25: รายการที่ต้องการในอาร์เรย์
Stan Kurilin

2
ฉันมักจะเห็นด้วยกับโบลชในนี้ แต่ varargs เป็นข้อยกเว้นที่ชัดเจนกฎ ...
Joeri Hendrickx

0

เมื่อทำงานกับอาร์เรย์ประเภททั่วไปฉันถูกบังคับให้ผ่านการอ้างอิงถึงประเภททั่วไป ด้วยวิธีนี้ฉันสามารถทำรหัสทั่วไปโดยใช้ java.lang.reflect.Array

http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html


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