Memanggil metode Java varargs dengan argumen nol tunggal?

97

Jika saya memiliki metode Java vararg foo(Object ...arg)dan saya memanggil foo(null, null), saya memiliki keduanya arg[0]dan arg[1]sebagai nulls. Tetapi jika saya menelepon foo(null), argitu sendiri adalah nol. Mengapa ini terjadi?

Bagaimana saya harus menelepon foosehingga foo.length == 1 && foo[0] == nulladalah true?

pathikrit
sumber

Jawaban:

95

Masalahnya adalah ketika Anda menggunakan literal null, Java tidak tahu tipe apa yang seharusnya. Ini bisa berupa Objek null, atau bisa juga berupa array Objek null. Untuk satu argumen, ia mengasumsikan yang terakhir.

Anda punya dua pilihan. Transmisikan null secara eksplisit ke Object atau panggil metode menggunakan variabel yang diketik dengan kuat. Lihat contoh di bawah ini:

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));
   }
}

Keluaran:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Mike Deck
sumber
Akan membantu orang lain jika Anda telah mencantumkan asListmetode dalam sampel, dan tujuannya.
Arun Kumar
5
@ArunKumar asList()adalah impor statis dari java.util.Arrayskelas. Saya hanya berasumsi bahwa itu sudah jelas. Meskipun sekarang saya memikirkannya, saya mungkin seharusnya hanya menggunakan Arrays.toString()karena satu-satunya alasan itu diubah menjadi Daftar adalah agar itu akan dicetak dengan cantik.
Mike Deck
23

Anda membutuhkan pemeran eksplisit untuk Object:

foo((Object) null);

Jika tidak, argumennya diasumsikan sebagai seluruh larik yang diwakili oleh vararg.

Bozho
sumber
Tidak juga, lihat postingan saya.
Buhake Sindi
Ups, sama di sini ... maaf, minyak tengah malam.
Buhake Sindi
6

Kasus Uji untuk menggambarkan ini:

Kode Java dengan deklarasi metode pengambilan vararg (yang kebetulan statis):

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;
    }
}

Kode Java ini (kasus uji JUnit4) memanggil yang di atas (kami menggunakan kasus uji untuk tidak menguji apa pun, hanya untuk menghasilkan beberapa keluaran):

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"}));
    }
}

Menjalankan ini sebagai pengujian JUnit menghasilkan:

sendNothing (): accept 'x' adalah array berukuran 0
sendNullWithNoCast (): diterima 'x' adalah null
sendNullWithCastToString (): diterima 'x' adalah larik berukuran 1
sendNullWithCastToArray (): diterima 'x' adalah null
sendOneValue (): diterima 'x' adalah larik berukuran 1
sendThreeValues ​​(): diterima 'x' adalah larik berukuran 3
sendArray (): accept 'x' adalah larik berukuran 3

Untuk membuatnya lebih menarik, panggil receive()fungsi dari Groovy 2.1.2 dan lihat apa yang terjadi. Ternyata hasilnya tidak sama! Ini mungkin bug.

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"
    }

}

Menjalankan ini sebagai pengujian JUnit menghasilkan yang berikut, dengan perbedaan pada Java yang disorot dalam huruf tebal.

sendNothing (): accept 'x' adalah array berukuran 0
sendNullWithNoCast (): diterima 'x' adalah null
sendNullWithCastToString (): diterima 'x' adalah null
sendNullWithCastToArray (): diterima 'x' adalah null
sendOneValue (): diterima 'x' adalah larik berukuran 1
sendThreeValues ​​(): diterima 'x' adalah larik berukuran 3
sendArray (): accept 'x' adalah larik berukuran 3
David Tonhofer
sumber
ini mempertimbangkan juga memanggil fungsi variadic tanpa parameter
bebbo
3

Ini karena metode varargs bisa dipanggil dengan larik aktual daripada serangkaian elemen larik. Saat Anda memberikan ambigu nulldengan sendirinya, ini mengasumsikan bahwa nulladalah Object[]. Mentransmisikan nullke Objectakan memperbaiki ini.

ColinD
sumber
1

saya lebih memilih

foo(new Object[0]);

untuk menghindari pengecualian pointer Null.

Semoga membantu.

Deepak Garg
sumber
14
Nah, jika itu adalah metode vararg, mengapa tidak disebut seperti itu saja foo()?
BrainStorm.exe
@ BrainStorm.exe jawaban Anda harus ditandai sebagai jawaban. Terima kasih.
MDP
1

Urutan resolusi kelebihan metode adalah ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. Fase pertama melakukan resolusi kelebihan beban tanpa mengizinkan konversi tinju atau unboxing, atau penggunaan pemanggilan metode arity variabel. Jika tidak ada metode yang dapat diterapkan yang ditemukan selama fase ini, maka pemrosesan dilanjutkan ke fase kedua.

    Ini menjamin bahwa panggilan apa pun yang valid dalam bahasa pemrograman Java sebelum Java SE 5.0 tidak dianggap ambigu sebagai hasil dari pengenalan metode arity variabel, tinju implisit dan / atau unboxing. Namun, deklarasi metode arity variabel (§8.4.1) dapat mengubah metode yang dipilih untuk ekspresi pemanggilan metode metode tertentu, karena metode arity variabel diperlakukan sebagai metode arity tetap pada fase pertama. Misalnya, mendeklarasikan m (Object ...) di kelas yang sudah mendeklarasikan m (Object) menyebabkan m (Object) tidak lagi dipilih untuk beberapa ekspresi pemanggilan (seperti m (null)), seperti m (Object [] ) lebih spesifik.

  2. Fase kedua melakukan resolusi kelebihan beban sambil mengizinkan tinju dan unboxing, tetapi masih menghalangi penggunaan pemanggilan metode arity variabel. Jika tidak ada metode yang dapat diterapkan yang ditemukan selama fase ini, maka pemrosesan dilanjutkan ke fase ketiga.

    Ini memastikan bahwa suatu metode tidak pernah dipilih melalui pemanggilan metode arity variabel jika itu berlaku melalui pemanggilan metode arity tetap.

  3. Fase ketiga memungkinkan overloading untuk digabungkan dengan metode variabel arity, tinju, dan unboxing.

foo(null)cocok foo(Object... arg)dengan arg = nulldi fase pertama. arg[0] = nullakan menjadi fase ketiga, yang tidak pernah terjadi.

Alexey Romanov
sumber