Gọi phương thức varargs Java với đối số null duy nhất?


97

Nếu tôi có một phương thức Java vararg foo(Object ...arg)và tôi gọi foo(null, null), tôi có cả hai arg[0]arg[1]dưới dạng nulls. Nhưng nếu tôi gọi foo(null), argbản thân nó là null. Tại sao chuyện này đang xảy ra?

Làm thế nào tôi nên gọi foonhư vậy foo.length == 1 && foo[0] == nulltrue?

Câu trả lời:


95

Vấn đề là khi bạn sử dụng nghĩa đen, Java không biết nó phải là kiểu gì. Nó có thể là một Đối tượng rỗng, hoặc nó có thể là một mảng Đối tượng rỗng. Đối với một đối số duy nhất, nó giả định điều sau.

Bạn có hai sự lựa chọn. Truyền null một cách rõ ràng tới Object hoặc gọi phương thức bằng cách sử dụng một biến được gõ mạnh. Xem ví dụ bên dưới:

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

Đầu ra:

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

Sẽ hữu ích cho những người khác nếu bạn đã liệt kê asListphương pháp trong mẫu và mục đích của nó.
Arun Kumar

5
@ArunKumar asList()là một nhập tĩnh từ java.util.Arrayslớp. Tôi chỉ cho rằng đó là điều hiển nhiên. Mặc dù bây giờ tôi đang nghĩ về nó, có lẽ tôi nên sử dụng Arrays.toString()vì lý do duy nhất nó được chuyển đổi thành Danh sách là vì vậy nó sẽ in đẹp.
Mike Deck

23

Bạn cần một dàn diễn viên rõ ràng để Object:

foo((Object) null);

Nếu không, đối số được giả định là toàn bộ mảng mà các varargs đại diện.


Không thực sự, hãy xem bài viết của tôi.
Buhake Sindi

Rất tiếc, cũng ở đây ... xin lỗi, dầu nửa đêm.
Buhake Sindi

6

Một trường hợp thử nghiệm để minh họa điều này:

Mã Java với khai báo phương thức lấy vararg (xảy ra là tĩnh):

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

Mã Java này (trường hợp thử nghiệm JUnit4) gọi như trên (chúng tôi đang sử dụng trường hợp thử nghiệm không phải để kiểm tra bất kỳ thứ gì, chỉ để tạo một số đầu ra):

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

Chạy điều này như một thử nghiệm JUnit mang lại:

sendNothing (): nhận được 'x' là một mảng có kích thước 0
sendNullWithNoCast (): nhận được 'x' là null
sendNullWithCastToString (): nhận được 'x' là một mảng có kích thước 1
sendNullWithCastToArray (): nhận được 'x' là null
sendOneValue (): nhận được 'x' là một mảng có kích thước 1
sendThreeValues ​​(): đã nhận 'x' là một mảng có kích thước 3
sendArray (): nhận được 'x' là một mảng có kích thước 3

Để làm cho điều này thú vị hơn, hãy gọi receive()hàm từ Groovy 2.1.2 và xem điều gì sẽ xảy ra. Hóa ra là kết quả không giống nhau! Đây có thể là một lỗi.

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

}

Chạy điều này như một bài kiểm tra JUnit cho kết quả như sau, với sự khác biệt so với Java được tô đậm.

sendNothing (): nhận được 'x' là một mảng có kích thước 0
sendNullWithNoCast (): nhận được 'x' là null
sendNullWithCastToString (): nhận được 'x' là null
sendNullWithCastToArray (): nhận được 'x' là null
sendOneValue (): nhận được 'x' là một mảng có kích thước 1
sendThreeValues ​​(): đã nhận 'x' là một mảng có kích thước 3
sendArray (): nhận được 'x' là một mảng có kích thước 3

điều này cũng coi là gọi hàm variadic mà không có tham số
bebbo

3

Điều này là do một phương thức varargs có thể được gọi với một mảng thực tế hơn là một chuỗi các phần tử mảng. Khi bạn cung cấp cho nó những điều không rõ ràng null, nó sẽ giả định nulllà một Object[]. Truyền nulltới Objectsẽ khắc phục điều này.


1

tôi thích

foo(new Object[0]);

để tránh ngoại lệ con trỏ Null.

Hy vọng nó giúp.


14
Vâng, nếu nó là một phương thức vararg, tại sao không chỉ cần gọi nó như thế foo()?
BrainStorm.exe

@ BrainStorm.exe câu trả lời của bạn phải được đánh dấu là câu trả lời. cảm ơn.
MDP

1

Thứ tự giải quyết quá tải phương thức là ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. Giai đoạn đầu tiên thực hiện giải quyết quá tải mà không cho phép chuyển đổi quyền anh hoặc mở hộp, hoặc sử dụng lệnh gọi phương thức arity biến. Nếu không tìm thấy phương pháp áp dụng nào trong giai đoạn này thì quá trình xử lý tiếp tục sang giai đoạn thứ hai.

    Điều này đảm bảo rằng bất kỳ lệnh gọi nào hợp lệ trong ngôn ngữ lập trình Java trước Java SE 5.0 đều không bị coi là mơ hồ do kết quả của việc giới thiệu các phương thức arity biến, quyền anh ngầm và / hoặc mở hộp. Tuy nhiên, việc khai báo một phương thức arity biến (§8.4.1) có thể thay đổi phương thức được chọn cho một biểu thức gọi phương thức phương thức nhất định, vì một phương thức arity biến được coi là một phương thức arity cố định trong giai đoạn đầu. Ví dụ: khai báo m (Đối tượng ...) trong một lớp đã khai báo m (Đối tượng) khiến m (Đối tượng) không còn được chọn cho một số biểu thức gọi (chẳng hạn như m (null)), như m (Đối tượng [] ) cụ thể hơn.

  2. Giai đoạn thứ hai thực hiện giải quyết quá tải trong khi cho phép đấm bốc và mở hộp, nhưng vẫn loại trừ việc sử dụng lệnh gọi phương thức arity biến. Nếu không tìm thấy phương pháp áp dụng nào trong giai đoạn này thì quá trình xử lý sẽ tiếp tục sang giai đoạn thứ ba.

    Điều này đảm bảo rằng một phương thức không bao giờ được chọn thông qua việc gọi phương thức arity biến nếu nó có thể áp dụng được thông qua việc gọi phương thức arity cố định.

  3. Giai đoạn thứ ba cho phép quá tải được kết hợp với các phương pháp hiếm có thay đổi, quyền anh và unboxing.

foo(null)phù hợp foo(Object... arg)với arg = nulltrong giai đoạn đầu tiên. arg[0] = nullsẽ là giai đoạn thứ ba, điều này không bao giờ xảy ra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.