การเรียกใช้เมธอด Java varargs ด้วยอาร์กิวเมนต์ null เดียว?


97

ถ้าฉันมีวิธี vararg Java foo(Object ...arg)และฉันเรียกfoo(null, null)ฉันมีทั้งสองอย่างarg[0]และarg[1]เป็นnulls แต่ถ้าผมเรียกfoo(null), argตัวเองเป็นโมฆะ เหตุใดจึงเกิดขึ้น

จะเรียกfooเช่นนั้นfoo.length == 1 && foo[0] == nullได้trueอย่างไร?

คำตอบ:


95

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

คุณมีสองทางเลือก แคสต์ null อย่างชัดเจนไปยัง Object หรือเรียกใช้เมธอดโดยใช้ตัวแปรที่พิมพ์มาก ดูตัวอย่างด้านล่าง:

public class Temp{
   public static void main(String[] args){
      foo("a", "b", "c");
      foo(null, null);
      foo((Object)null);
      Object bar = null;
      foo(bar);
   }

   private static void foo(Object...args) {
      System.out.println("foo called, args: " + asList(args));
   }
}

เอาท์พุต:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]

จะเป็นประโยชน์กับผู้อื่นหากคุณระบุasListวิธีการไว้ในตัวอย่างและวัตถุประสงค์
อรุณกุมาร

5
@ArunKumar asList()เป็นการนำเข้าแบบคงที่จากjava.util.Arraysคลาส ฉันแค่คิดว่ามันชัดเจน แม้ว่าตอนนี้ฉันกำลังคิดอยู่ แต่ฉันก็น่าจะเพิ่งใช้Arrays.toString()เพราะเหตุผลเดียวที่มันถูกแปลงเป็น List ก็คือมันจะพิมพ์ออกมาได้สวย
Mike Deck

24

คุณต้องแคสต์อย่างชัดเจนเพื่อObject:

foo((Object) null);

มิฉะนั้นอาร์กิวเมนต์จะถือว่าเป็นอาร์เรย์ทั้งหมดที่ varargs เป็นตัวแทน


ไม่จริงดูโพสต์ของฉัน
Buhake Sindi

อ๊ะเหมือนกันที่นี่ ... ขออภัยน้ำมันเที่ยงคืน
Buhake Sindi

6

กรณีทดสอบเพื่อแสดงสิ่งนี้:

รหัส Java ที่มีการประกาศวิธีการ vararg (ซึ่งเป็นแบบคงที่):

public class JavaReceiver {
    public static String receive(String... x) {
        String res = ((x == null) ? "null" : ("an array of size " + x.length));
        return "received 'x' is " + res;
    }
}

โค้ด Java นี้ (กรณีทดสอบ JUnit4) เรียกสิ่งที่กล่าวมา (เรากำลังใช้กรณีทดสอบที่จะไม่ทดสอบอะไรเลยเพียงเพื่อสร้างเอาต์พุตบางส่วน):

import org.junit.Test;

public class JavaSender {

    @Test
    public void sendNothing() {
        System.out.println("sendNothing(): " + JavaReceiver.receive());
    }

    @Test
    public void sendNullWithNoCast() {
        System.out.println("sendNullWithNoCast(): " + JavaReceiver.receive(null));
    }

    @Test
    public void sendNullWithCastToString() {
        System.out.println("sendNullWithCastToString(): " + JavaReceiver.receive((String)null));
    }

    @Test
    public void sendNullWithCastToArray() {
        System.out.println("sendNullWithCastToArray(): " + JavaReceiver.receive((String[])null));
    }

    @Test
    public void sendOneValue() {
        System.out.println("sendOneValue(): " + JavaReceiver.receive("a"));
    }

    @Test
    public void sendThreeValues() {
        System.out.println("sendThreeValues(): " + JavaReceiver.receive("a", "b", "c"));
    }

    @Test
    public void sendArray() {
        System.out.println("sendArray(): " + JavaReceiver.receive(new String[]{"a", "b", "c"}));
    }
}

การรันสิ่งนี้เป็นการทดสอบ JUnit ให้ผล:

sendNothing (): ได้รับ 'x' คืออาร์เรย์ขนาด 0
sendNullWithNoCast (): ได้รับ 'x' เป็นโมฆะ
sendNullWithCastToString (): ได้รับ 'x' คืออาร์เรย์ขนาด 1
sendNullWithCastToArray (): ได้รับ 'x' เป็นโมฆะ
sendOneValue (): ได้รับ 'x' คืออาร์เรย์ขนาด 1
sendThreeValues ​​(): ได้รับ 'x' คืออาร์เรย์ขนาด 3
sendArray (): ได้รับ 'x' คืออาร์เรย์ขนาด 3

เพื่อให้สิ่งนี้น่าสนใจยิ่งขึ้นให้เรียกใช้receive()ฟังก์ชันจาก Groovy 2.1.2 และดูว่าเกิดอะไรขึ้น ปรากฎว่าผลลัพธ์ไม่เหมือนกัน! นี่อาจเป็นข้อบกพร่องแม้ว่า

import org.junit.Test

class GroovySender {

    @Test
    void sendNothing() {
        System.out << "sendNothing(): " << JavaReceiver.receive() << "\n"
    }

    @Test
    void sendNullWithNoCast() {
        System.out << "sendNullWithNoCast(): " << JavaReceiver.receive(null) << "\n"
    }

    @Test
    void sendNullWithCastToString() {
        System.out << "sendNullWithCastToString(): " << JavaReceiver.receive((String)null) << "\n"
    }

    @Test
    void sendNullWithCastToArray() {
        System.out << "sendNullWithCastToArray(): " << JavaReceiver.receive((String[])null) << "\n"
    }

    @Test
    void sendOneValue() {
        System.out << "sendOneValue(): " + JavaReceiver.receive("a") << "\n"
    }

    @Test
    void sendThreeValues() {
        System.out << "sendThreeValues(): " + JavaReceiver.receive("a", "b", "c") << "\n"
    }

    @Test
    void sendArray() {
        System.out << "sendArray(): " + JavaReceiver.receive( ["a", "b", "c"] as String[] ) << "\n"
    }

}

การรันสิ่งนี้เป็นการทดสอบ JUnit จะให้ผลดังต่อไปนี้โดยความแตกต่างของ Java จะถูกเน้นเป็นตัวหนา

sendNothing (): ได้รับ 'x' คืออาร์เรย์ขนาด 0
sendNullWithNoCast (): ได้รับ 'x' เป็นโมฆะ
sendNullWithCastToString (): ได้รับ 'x' เป็นโมฆะ
sendNullWithCastToArray (): ได้รับ 'x' เป็นโมฆะ
sendOneValue (): ได้รับ 'x' คืออาร์เรย์ขนาด 1
sendThreeValues ​​(): ได้รับ 'x' คืออาร์เรย์ขนาด 3
sendArray (): ได้รับ 'x' คืออาร์เรย์ขนาด 3

สิ่งนี้จะถือว่าเรียกฟังก์ชันตัวแปรโดยไม่มีพารามิเตอร์ด้วย
bebbo

3

เนื่องจากเมธอด varargs สามารถเรียกใช้กับอาร์เรย์จริงแทนที่จะเป็นชุดขององค์ประกอบอาร์เรย์ เมื่อคุณระบุสิ่งที่ไม่ชัดเจนnullด้วยตัวมันเองจะถือว่าnullเป็นObject[]ไฟล์. การส่งnullto Objectจะแก้ไขปัญหานี้


1

ฉันชอบ

foo(new Object[0]);

เพื่อหลีกเลี่ยงข้อยกเว้นของตัวชี้ Null

หวังว่าจะช่วยได้


14
ถ้าเป็นวิธี vararg ทำไมไม่เรียกว่า like foo()ล่ะ?
BrainStorm.exe

@ BrainStorm.exe คำตอบของคุณควรถูกระบุว่าเป็นคำตอบ ขอบคุณ.
MDP

1

ลำดับสำหรับวิธีการแก้ปัญหาการโอเวอร์โหลดคือ ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. ระยะแรกดำเนินการแก้ปัญหาโอเวอร์โหลดโดยไม่อนุญาตให้มีการแปลงแบบบ็อกซ์หรือการแกะกล่องหรือใช้การเรียกใช้เมธอด arity ตัวแปร หากไม่พบวิธีการที่ใช้ได้ในช่วงนี้การประมวลผลจะดำเนินต่อไปยังเฟสที่สอง

    สิ่งนี้รับประกันได้ว่าการเรียกใด ๆ ที่ถูกต้องในภาษาการเขียนโปรแกรม Java ก่อน Java SE 5.0 จะไม่ถือว่าคลุมเครืออันเป็นผลมาจากการแนะนำวิธี arity ตัวแปรการชกมวยโดยปริยายและ / หรือการแกะกล่อง อย่างไรก็ตามการประกาศตัวแปร arity method (§8.4.1) สามารถเปลี่ยนวิธีการที่เลือกไว้สำหรับนิพจน์การเรียกใช้ method method ที่กำหนดได้เนื่องจากวิธีการตัวแปร arity จะถือว่าเป็นวิธี arity คงที่ในระยะแรก ตัวอย่างเช่นการประกาศ m (Object ... ) ในคลาสที่ประกาศ m (Object) ไปแล้วทำให้ m (Object) ไม่สามารถเลือกได้อีกต่อไปสำหรับนิพจน์การเรียกใช้บางอย่าง (เช่น m (null)) เป็น m (Object [] ) มีความเฉพาะเจาะจงมากขึ้น

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

    สิ่งนี้ทำให้มั่นใจได้ว่าจะไม่มีการเลือกเมธอดผ่านการเรียกใช้เมธอด arity ตัวแปรหากใช้ได้ผ่านการเรียกใช้เมธอด arity คงที่

  3. ขั้นตอนที่สามช่วยให้สามารถรวมการโอเวอร์โหลดกับวิธีอาริตี้แบบแปรผันชกมวยและการแกะกล่องได้

foo(null)ตรงfoo(Object... arg)กับarg = nullในระยะแรก arg[0] = nullจะเป็นระยะที่สามซึ่งไม่เคยเกิดขึ้น

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