Khi nào bạn sử dụng varargs trong Java?


192

Tôi sợ sự khác biệt. Tôi không biết sử dụng chúng để làm gì.

Thêm vào đó, cảm thấy nguy hiểm khi để mọi người vượt qua nhiều cuộc tranh luận như họ muốn.

Một ví dụ về bối cảnh sẽ là một nơi tốt để sử dụng chúng là gì?


3
Tôi không thấy lý do tại sao nó sẽ "nguy hiểm". Nó không nguy hiểm hơn việc có một phương pháp được gọi là số lần lớn với các đối số khác nhau. Bạn có quan tâm với ngăn xếp? Sau đó, bạn không nên, bởi vì các vararg được ánh xạ tới các mảng được truyền bằng tham chiếu.
jfpoilpret

66
Chỉ muốn hòa nhập: không có gì sai khi tránh các tính năng ngôn ngữ mà bạn chưa (hoàn toàn) thoải mái. Tốt hơn nhiều so với việc sử dụng các tính năng bạn không hiểu! ;)
Steven

2
Đó là bình thường để sợ không biết. Để tìm hiểu thêm, hãy đọc về varargs tại đây docs.oracle.com/javase/tutorial/java/javaOO/argument.html . Bạn sẽ thấy, varargs không có gì phải sợ. Varargs rất hữu ích. Biết cách sử dụng varargs cung cấp cho bạn khả năng viết các phương thức như [PrintStream.format] (" docs.oracle.com/javase/7/docs/api/java/io/iêu , java.lang.Object ...)" ) :).
Nhà phát triển Marius ilėnas

1
Đây không phải là một lời chỉ trích về varargs, nhưng thực sự có một số lý do để "sợ" (cho đến khi bạn hiểu chính xác những hạn chế của varargs); đây là lý do tại sao chú thích @SafeVarargs tồn tại. stackoverflow.com/a/14252221/1593924
Jon Coombs

Câu trả lời:


150

Varargs rất hữu ích cho bất kỳ phương thức nào cần xử lý số lượng đối tượng không xác định . Một ví dụ điển hình là String.format. Chuỗi định dạng có thể chấp nhận bất kỳ số lượng tham số nào, vì vậy bạn cần một cơ chế để truyền vào bất kỳ số lượng đối tượng nào.

String.format("This is an integer: %d", myInt);
String.format("This is an integer: %d and a string: %s", myInt, myString);

5
Một tham số mảng cũng có thể nhận được số lượng đối tượng không xác định nhưng tham số varargs cho phép linh hoạt và thuận tiện hơn tại điểm gọi. Bạn có thể viết mã để xây dựng một mảng và vượt qua nó hoặc bạn có thể để Java làm điều đó cho bạn khi bạn chọn nhận nó trong một tham số varargs.
H2ONaCl

79

Một nguyên tắc tốt sẽ là:

"Sử dụng varargs cho bất kỳ phương thức (hoặc hàm tạo) nào cần một mảng T (bất kể kiểu T có thể) làm đầu vào".

Điều đó sẽ làm cho các cuộc gọi đến các phương thức này dễ dàng hơn (không cần phải làm new T[]{...}).

Bạn có thể mở rộng quy tắc này để bao gồm các phương thức với một List<T>đối số, với điều kiện là đối số này chỉ dành cho đầu vào (nghĩa là danh sách không được sửa đổi bởi phương thức).

Ngoài ra, tôi sẽ không sử dụng f(Object... args)vì nó trượt theo cách lập trình với các API không rõ ràng.

Về các ví dụ, tôi đã sử dụng nó trong DesignGridLayout , nơi tôi có thể thêm một vài JComponents trong một cuộc gọi:

layout.row().grid(new JLabel("Label")).add(field1, field2, field3);

Trong đoạn mã trên, phương thức add () được định nghĩa là add(JComponent... components).

Cuối cùng, việc thực hiện các phương thức như vậy phải quan tâm đến thực tế rằng nó có thể được gọi với một vararg trống! Nếu bạn muốn áp đặt ít nhất một đối số, thì bạn phải sử dụng một thủ thuật xấu xí như:

void f(T arg1, T... args) {...}

Tôi coi thủ thuật này là xấu vì việc thực hiện phương thức sẽ ít đơn giản hơn so với việc chỉ có T... argstrong danh sách đối số của nó.

Hy vọng điều này sẽ giúp làm rõ quan điểm về varargs.


1
Bạn đã xem xét thêm một kiểm tra điều kiện tiên quyết if (args.length == 0) throw new RuntimeException("foo");thay thế? (Vì người gọi đã vi phạm hợp đồng)
Micha Wiedenmann

24
Chà, mục đích của một API tốt là để ngăn chặn việc sử dụng sai càng sớm càng tốt, vì vậy vào thời gian biên dịch khi có thể, do đó, đề xuất cho void f(T arg1, T... args)điều đó luôn đảm bảo rằng nó không bao giờ được gọi mà không có đối số, không cần phải đợi cho đến khi chạy.
jfpoilpret

Tôi nghĩ rằng hầu hết thời gian một cuộc gọi đến một hàm chỉ varargs không có đối số nào sẽ chỉ là số tiền cũng không làm gì cả. Điểm là việc cung cấp không có đối số cho hàm varargs rất có thể sẽ không gây hại đáng kể.
WorldSEnder

Varargs rất hữu ích, nhưng chúng không tương đương với việc sử dụng một mảng. Varargs là không thể tái xác nhận. Vì vậy, giống như thuốc generic, chúng bị ảnh hưởng bởi loại tẩy xóa rất có thể là một hạn chế không thể chấp nhận được tùy thuộc vào công việc.
Julian

34

Tôi sử dụng varargs thường xuyên để xuất ra các bản ghi cho mục đích gỡ lỗi.

Khá nhiều lớp trong ứng dụng của tôi có phương thức debugPrint ():

private void debugPrint(Object... msg) {
    for (Object item : msg) System.out.print(item);
    System.out.println();
}

Sau đó, trong các phương thức của lớp, tôi có các cuộc gọi như sau:

debugPrint("for assignment ", hwId, ", student ", studentId, ", question ",
    serialNo, ", the grade is ", grade);

Khi tôi hài lòng rằng mã của tôi đang hoạt động, tôi nhận xét mã trong phương thức debugPrint () để các nhật ký sẽ không chứa quá nhiều thông tin không liên quan và không mong muốn, nhưng tôi có thể để các cuộc gọi riêng lẻ đến debugPrint (). Sau đó, nếu tôi tìm thấy một lỗi, tôi chỉ cần bỏ mã debugPrint () và tất cả các lệnh gọi tới debugPrint () của tôi được kích hoạt lại.

Tất nhiên, thay vào đó, tôi có thể dễ dàng tránh các varargs và thực hiện các thao tác sau:

private void debugPrint(String msg) {
    System.out.println(msg);
}

debugPrint("for assignment " + hwId + ", student " + studentId + ", question "
    + serialNo + ", the grade is " + grade);

Tuy nhiên, trong trường hợp này, khi tôi nhận xét mã debugPrint (), máy chủ vẫn phải trải qua sự cố nối tất cả các biến trong mỗi lệnh gọi tới debugPrint (), mặc dù không có gì được thực hiện với chuỗi kết quả. Tuy nhiên, nếu tôi sử dụng varargs, máy chủ chỉ phải đặt chúng vào một mảng trước khi nó nhận ra rằng nó không cần chúng. Rất nhiều thời gian được tiết kiệm.


4
Bạn có thể thực hiện một phương thức gỡ lỗi trong siêu lớp để in bất kỳ đối tượng nào bằng cách sử dụng sự phản chiếu.
Lluis Martinez

12

Các biến có thể được sử dụng khi chúng ta không chắc chắn về số lượng đối số được truyền trong một phương thức. Nó tạo ra một mảng các tham số có độ dài không xác định trong nền và một tham số như vậy có thể được coi là một mảng trong thời gian chạy.

Nếu chúng ta có một phương thức bị quá tải để chấp nhận số lượng tham số khác nhau, thì thay vì quá tải các phương thức khác nhau, chúng ta chỉ có thể sử dụng khái niệm varargs.

Ngoài ra, khi loại tham số sẽ thay đổi thì việc sử dụng "Đối tượng ... kiểm tra" sẽ đơn giản hóa mã rất nhiều.

Ví dụ:

public int calculate(int...list) {
    int sum = 0;
    for (int item : list) {
        sum += item;
    }
    return sum;
}

Ở đây gián tiếp một mảng kiểu int (danh sách) được truyền dưới dạng tham số và được coi là một mảng trong mã.

Để hiểu rõ hơn hãy theo liên kết này (nó giúp tôi hiểu rất rõ về khái niệm này): http://www.javadb.com/USE-varargs-in-java

PS: Ngay cả tôi cũng sợ sử dụng varargs khi tôi không sử dụng nó. Nhưng bây giờ tôi đã quen với nó. Như người ta nói: "Chúng tôi bám vào cái đã biết, sợ cái chưa biết", vì vậy chỉ cần sử dụng nó nhiều nhất có thể và bạn cũng sẽ bắt đầu thích nó :)


4
C # Tương đương của varargs là "params". Nó cũng làm điều tương tự và chấp nhận số lượng tham số thay đổi. Xem phần này để hiểu rõ hơn: dotnetperls.com/params
Sarvan

11

Varargs là tính năng được thêm vào trong phiên bản java 1.5.

Tại sao phải sử dụng cái này?

  1. Điều gì xảy ra nếu, bạn không biết số lượng đối số cần truyền cho một phương thức?
  2. Điều gì xảy ra nếu, bạn muốn truyền số lượng đối số không giới hạn cho một phương thức?

Làm thế nào điều này hoạt động?

Nó tạo ra một mảng với các đối số đã cho & truyền mảng cho phương thức.

Thí dụ :

public class Solution {



    public static void main(String[] args) {
        add(5,7);
        add(5,7,9);
    }

    public static void add(int... s){
        System.out.println(s.length);
        int sum=0;
        for(int num:s)
            sum=sum+num;
        System.out.println("sum is "+sum );
    }

}

Đầu ra:

2

tổng là 12

3

tổng là 21


6

Tôi cũng có một nỗi sợ liên quan đến varargs:

Nếu người gọi chuyển trong một mảng rõ ràng cho phương thức (trái ngược với nhiều tham số), bạn sẽ nhận được một tham chiếu được chia sẻ đến mảng đó.

Nếu bạn cần lưu trữ mảng này trong nội bộ, bạn có thể muốn sao chép nó trước để tránh người gọi có thể thay đổi nó sau.

 Object[] args = new Object[] { 1, 2, 3} ;

 varArgMethod(args);  // not varArgMethod(1,2,3);

 args[2] = "something else";  // this could have unexpected side-effects

Mặc dù điều này không thực sự khác với việc chuyển vào bất kỳ loại đối tượng nào có trạng thái có thể thay đổi sau đó, vì mảng thường là (trong trường hợp một cuộc gọi có nhiều đối số thay vì một mảng), một đối tượng mới được tạo bởi trình biên dịch bên trong mà bạn có thể an toàn sử dụng, đây chắc chắn là hành vi bất ngờ.


1
Đúng, nhưng ví dụ này có vẻ hơi xa vời. Đây có phải là khả năng mọi người sẽ sử dụng API của bạn theo cách này?
jfpoilpret

2
Bạn không bao giờ biết ... Và đặc biệt là khi số lượng đối số không được mã hóa cứng ở phía gọi, nhưng cũng được thu thập nói vào một danh sách trong một vòng lặp, truyền vào một mảng không phải là hiếm.
Thilo

5
Tôi nghĩ rằng ví dụ trường hợp sử dụng của bạn, nói chung, không phải là không thể. Người ta có thể lập luận rằng API của bạn không nên sử dụng mảng đầu vào theo cách này và nếu có, nó phải ghi lại nó.
Lawrence Dol

quá tải phương pháp để hỗ trợ cả hai; argMethod (Object .. objList) argMethod (Object [] objList)
thetoolman

2
@thetoolman: Tôi không nghĩ điều đó là có thể. Nó sẽ được coi là một phương pháp trùng lặp.
Thilo

1

Tôi sử dụng varargs thường xuyên cho các nhà xây dựng có thể lấy một số loại đối tượng bộ lọc. Ví dụ, một phần lớn trong hệ thống của chúng tôi dựa trên Hadoop dựa trên Mapper xử lý việc tuần tự hóa và giải tuần tự hóa các mục cho JSON và áp dụng một số bộ xử lý mà mỗi mục lấy một mục nội dung và sửa đổi và trả về hoặc trả về null từ chối.


1

Trong tài liệu Java của Var-Args, việc sử dụng var args khá rõ ràng:

http://docs.oracle.com/javase/1.5.0/docs/guide/lingu/varargs.html

về cách sử dụng nó nói:

"Vì vậy, khi nào bạn nên sử dụng varargs? Là một khách hàng, bạn nên tận dụng chúng bất cứ khi nào API cung cấp chúng. Các ứng dụng quan trọng trong API cốt lõi bao gồm phản chiếu, định dạng thông báo và cơ sở printf mới. Là một nhà thiết kế API, bạn nên sử dụng chúng Nói một cách tiết kiệm, chỉ khi lợi ích thực sự hấp dẫn. Nói chung, bạn không nên quá tải một phương thức varargs, hoặc sẽ rất khó để các lập trình viên tìm ra quá tải nào được gọi. "

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.