ฉันสามารถส่งอาเรย์เป็นอาร์กิวเมนต์ไปยังเมธอดที่มีอาร์กิวเมนต์ของตัวแปรใน Java ได้หรือไม่?


276

ฉันต้องการที่จะสามารถสร้างฟังก์ชั่นเช่น:

class A {
  private String extraVar;
  public String myFormat(String format, Object ... args){
    return String.format(format, extraVar, args);
  }
}

นี่คือปัญหาที่argsจะถือว่าเป็นObject[]ในวิธีการmyFormatและทำให้เป็นอาร์กิวเมนต์เดียวString.formatในขณะที่ฉันต้องการทุกเดียวObjectในargsการผ่านเป็นอาร์กิวเมนต์ใหม่ เนื่องจากString.formatเป็นวิธีที่มีอาร์กิวเมนต์ที่หลากหลายจึงควรเป็นไปได้

หากเป็นไปไม่ได้มีวิธีการเช่นนี้String.format(String format, Object[] args)หรือไม่? ในกรณีนั้นฉันสามารถผนวกextraVarกับการargsใช้อาร์เรย์ใหม่และส่งผ่านไปยังวิธีการนั้นได้


8
ฉันอดไม่ได้ที่จะสงสัยว่าทำไมคำถามนี้เป็นคำถามแบบ "ถูกต้อง" คุณลองมันไม่ได้เหรอ? อย่าทำมากเกินไปถามคุณจะทำอันตรายตัวเองมากกว่าดี
kamasheto

21
จริงเพียงพอสิ่งนี้สามารถทดสอบได้ง่าย อย่างไรก็ตามสิ่งที่ดีเกี่ยวกับคำถามเช่นนี้คือมันทำให้เกิดหัวข้อและเรียกร้องคำตอบที่น่าสนใจ
akf

2
ฉันลองใช้รหัสข้างต้นโดยมีความตั้งใจที่จะส่งอาร์เรย์เป็นอาร์กิวเมนต์สำหรับวิธีการ อย่างไรก็ตามฉันไม่ได้ตระหนักว่าฉันควรจะเพิ่ม extraVar ไปเป็น args ก่อน เมื่อคุณรู้ว่าอาร์กิวเมนต์ของตัวแปรนั้นถือเป็นอาเรย์ (แม้จะอยู่นอกเมธอด) นี่เป็นเหตุผลที่ค่อนข้างสมเหตุสมผล
user362382

คำตอบ:


181

ประเภทพื้นฐานของวิธี variadic คือfunction(Object... args) function(Object[] args)Sun ได้เพิ่ม varargs ในลักษณะนี้เพื่อรักษาความเข้ากันได้ย้อนหลัง

ดังนั้นคุณก็ควรจะสามารถย่อหน้าextraVarไปและโทรargsString.format(format, args)


1
การส่งอาร์กิวเมนต์ชนิดX[]เป็นวิธีx(X... xs)ให้คำเตือนต่อไปนี้ใน Eclipse:Type X[] of the last argument to method x(X...) doesn't exactly match the vararg parameter type. Cast to X[] to confirm the non-varargs invocation, or pass individual arguments of type X for a varargs invocation.
ลุคฮัทชิสัน

318

ใช่เป็นเพียงน้ำตาลประโยคสำหรับT...T[]

JLS 8.4.1 รูปแบบพารามิเตอร์

พารามิเตอร์ทางการสุดท้ายในรายการเป็นพิเศษ มันอาจเป็นพารามิเตอร์arityตัวแปรที่ระบุโดย elipsis ตามประเภท

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

นี่คือตัวอย่างที่แสดงให้เห็น:

public static String ezFormat(Object... args) {
    String format = new String(new char[args.length])
        .replace("\0", "[ %s ]");
    return String.format(format, args);
}
public static void main(String... args) {
    System.out.println(ezFormat("A", "B", "C"));
    // prints "[ A ][ B ][ C ]"
}

และใช่ดังกล่าวข้างต้นmainเป็นวิธีการที่ถูกต้องเพราะอีกครั้งเป็นเพียงString... String[]นอกจากนี้เนื่องจากมีอาร์เรย์ covariant ที่String[]เป็นObject[]เพื่อให้คุณยังสามารถโทรezFormat(args)ทางใดทางหนึ่ง

ดูสิ่งนี้ด้วย


Varargs gotchas # 1: การผ่าน null

วิธีการแก้ไข varargs นั้นค่อนข้างซับซ้อนและบางครั้งก็ทำสิ่งที่อาจทำให้คุณประหลาดใจ

ลองพิจารณาตัวอย่างนี้:

static void count(Object... objs) {
    System.out.println(objs.length);
}

count(null, null, null); // prints "3"
count(null, null); // prints "2"
count(null); // throws java.lang.NullPointerException!!!

เนื่องจากวิธีการ varargs ได้รับการแก้ไขจะเรียกคำสั่งสุดท้ายด้วยobjs = nullซึ่งแน่นอนว่าจะทำให้มีNullPointerException objs.lengthหากคุณต้องการให้nullอาร์กิวเมนต์หนึ่งตัวกับพารามิเตอร์ varargs คุณสามารถทำอย่างใดอย่างหนึ่งต่อไปนี้:

count(new Object[] { null }); // prints "1"
count((Object) null); // prints "1"

คำถามที่เกี่ยวข้อง

ต่อไปนี้เป็นตัวอย่างของคำถามที่ผู้คนถามเมื่อจัดการกับ varargs:


Vararg gotchas # 2: การเพิ่มอาร์กิวเมนต์พิเศษ

ดังที่คุณทราบแล้วสิ่งต่อไปนี้จะไม่ทำงาน

    String[] myArgs = { "A", "B", "C" };
    System.out.println(ezFormat(myArgs, "Z"));
    // prints "[ [Ljava.lang.String;@13c5982 ][ Z ]"

เนื่องจากวิธีการ varargs ทำงานezFormatจริงได้รับ 2 ข้อโต้แย้งเป็นครั้งแรกที่เป็นที่เป็นที่สองString[] Stringหากคุณกำลังส่งอาเรย์ไปยัง varargs และคุณต้องการให้อิลิเมนต์นั้นได้รับการยอมรับว่าเป็นอาร์กิวเมนต์แต่ละตัวและคุณต้องเพิ่มอาร์กิวเมนต์พิเศษจากนั้นคุณไม่มีทางเลือกนอกจากสร้างอาร์เรย์อื่นที่รองรับองค์ประกอบเพิ่มเติม

ต่อไปนี้เป็นวิธีการช่วยเหลือที่มีประโยชน์:

static <T> T[] append(T[] arr, T lastElement) {
    final int N = arr.length;
    arr = java.util.Arrays.copyOf(arr, N+1);
    arr[N] = lastElement;
    return arr;
}
static <T> T[] prepend(T[] arr, T firstElement) {
    final int N = arr.length;
    arr = java.util.Arrays.copyOf(arr, N+1);
    System.arraycopy(arr, 0, arr, 1, N);
    arr[0] = firstElement;
    return arr;
}

ตอนนี้คุณสามารถทำสิ่งต่อไปนี้:

    String[] myArgs = { "A", "B", "C" };
    System.out.println(ezFormat(append(myArgs, "Z")));
    // prints "[ A ][ B ][ C ][ Z ]"

    System.out.println(ezFormat(prepend(myArgs, "Z")));
    // prints "[ Z ][ A ][ B ][ C ]"

Varargs gotchas # 3: ผ่านอาร์เรย์แบบดั้งเดิม

มันไม่ "ทำงาน":

    int[] myNumbers = { 1, 2, 3 };
    System.out.println(ezFormat(myNumbers));
    // prints "[ [I@13c5982 ]"

Varargs ใช้ได้กับประเภทอ้างอิงเท่านั้น การล็อคอัตโนมัติไม่สามารถใช้กับอาเรย์ดั้งเดิมได้ ผลงานดังต่อไปนี้:

    Integer[] myNumbers = { 1, 2, 3 };
    System.out.println(ezFormat(myNumbers));
    // prints "[ 1 ][ 2 ][ 3 ]"

2
การใช้ Object ... พารามิเตอร์ตัวแปรทำให้ยากต่อการใช้ลายเซ็นเพิ่มเติมเนื่องจากคอมไพเลอร์ระบุความกำกวมระหว่างลายเซ็นซึ่งแม้แต่อาร์กิวเมนต์ดั้งเดิมก็สามารถ autoboxed กับ Object ได้
ปลาไหล gEEEE

7
มี gotcha ที่ขาดหายไป: หากวิธีการของคุณมีพารามิเตอร์ประเภทสุดท้าย ... และคุณเรียกมันว่าผ่านสตริง [] คอมไพเลอร์ไม่ทราบว่าอาร์เรย์ของคุณเป็นรายการแรกของ varargs หรือเทียบเท่ากับ varargs ทั้งหมด มันจะเตือนคุณ
Snicolas

การส่งอาร์กิวเมนต์ชนิดX[]เป็นวิธีx(X... xs)ให้คำเตือนต่อไปนี้ใน Eclipse:Type X[] of the last argument to method x(X...) doesn't exactly match the vararg parameter type. Cast to X[] to confirm the non-varargs invocation, or pass individual arguments of type X for a varargs invocation.
ลุคฮัทชิสัน

23

มันโอเคที่จะผ่านอาร์เรย์ - อันที่จริงมันมีจำนวนเท่ากัน

String.format("%s %s", "hello", "world!");

เป็นเช่นเดียวกับ

String.format("%s %s", new Object[] { "hello", "world!"});

มันเป็นเพียงน้ำตาลเชิงซ้อนคอมไพเลอร์แปลงอันแรกเป็นวินาทีเนื่องจากวิธีการพื้นฐานคาดว่าจะมีอาร์เรย์สำหรับพารามิเตอร์vararg

ดู


4

jasonmp85 String.formatที่ถูกต้องเกี่ยวกับการผ่านอาร์เรย์ที่แตกต่างกันไป ขนาดของอาร์เรย์ไม่สามารถเปลี่ยนแปลงได้เมื่อสร้างขึ้นแล้วดังนั้นคุณต้องผ่านอาร์เรย์ใหม่แทนการปรับเปลี่ยนอาร์เรย์ที่มีอยู่

Object newArgs = new Object[args.length+1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = extraVar; 
String.format(format, extraVar, args);

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