Chú thích Java SafeVarargs, có tồn tại một tiêu chuẩn hoặc thực tiễn tốt nhất không?


175

Gần đây tôi đã bắt gặp @SafeVarargschú thích java . Googling cho những gì làm cho một hàm biến đổi trong Java không an toàn khiến tôi khá bối rối (ngộ độc heap? Các loại đã xóa?), Vì vậy tôi muốn biết một vài điều:

  1. Điều gì làm cho một hàm Java biến đổi không an toàn theo @SafeVarargsnghĩa (tốt nhất là được giải thích dưới dạng một ví dụ chuyên sâu)?

  2. Tại sao chú thích này lại tùy theo ý của lập trình viên? Đây không phải là thứ mà trình biên dịch có thể kiểm tra sao?

  3. Có một số tiêu chuẩn phải tuân thủ để đảm bảo chức năng của mình thực sự an toàn không? Nếu không, các thực hành tốt nhất để đảm bảo nó là gì?


1
Bạn đã thấy ví dụ (và giải thích) trong JavaDoc chưa?
jlordo

2
Đối với câu hỏi thứ ba của bạn, một thực hành là luôn luôn có một yếu tố đầu tiên và các yếu tố khác : retType myMethod(Arg first, Arg... others). Nếu bạn không chỉ định first, một mảng trống được cho phép và rất có thể bạn có một phương thức có cùng tên với cùng kiểu trả về không có đối số, điều đó có nghĩa là JVM sẽ khó xác định phương thức nào sẽ được gọi.
fge

4
@jlordo Tôi đã làm, nhưng tôi không hiểu tại sao nó được đưa ra trong bối cảnh varargs vì người ta có thể dễ dàng khắc phục tình huống này bên ngoài chức năng varargs (đã xác minh điều này, biên dịch với các cảnh báo và lỗi an toàn loại dự kiến ​​trong thời gian chạy) ..
Oren

Câu trả lời:


244

1) Có nhiều ví dụ trên Internet và trên StackOverflow về vấn đề cụ thể với thuốc generic và varargs. Về cơ bản, đó là khi bạn có số lượng đối số của loại tham số loại:

<T> void foo(T... args);

Trong Java, varargs là một đường cú pháp trải qua một "cách viết lại" đơn giản tại thời gian biên dịch: một tham số varargs của loại X...được chuyển đổi thành một tham số kiểu X[]; và mỗi khi có một cuộc gọi được thực hiện cho phương thức varargs này, trình biên dịch sẽ thu thập tất cả các "đối số biến" có trong tham số varargs và tạo ra một mảng giống như new X[] { ...(arguments go here)... }.

Điều này hoạt động tốt khi các loại varargs là cụ thể String.... Khi đó là một biến kiểu như thế T..., nó cũng hoạt động khi Tđược biết là một kiểu cụ thể cho cuộc gọi đó. ví dụ như nếu phương pháp trên là một phần của một lớp Foo<T>, và bạn có một Foo<String>tài liệu tham khảo, sau đó gọi foovào nó sẽ ổn bởi vì chúng ta biết TStringtại thời điểm đó trong các mã.

Tuy nhiên, nó không hoạt động khi "giá trị" của Tlà một tham số loại khác. Trong Java, không thể tạo một mảng kiểu thành phần tham số kiểu ( new T[] { ... }). Vì vậy, Java thay vì sử dụng new Object[] { ... }(ở đây Objectlà giới hạn trên của T; nếu có giới hạn trên là một cái gì đó khác, thì nó sẽ thay thế Object), và sau đó đưa ra cảnh báo cho trình biên dịch.

Vì vậy, có gì sai với việc tạo ra new Object[]thay vì new T[]hoặc bất cứ điều gì? Chà, mảng trong Java biết loại thành phần của chúng khi chạy. Do đó, đối tượng mảng đã truyền sẽ có kiểu thành phần sai khi chạy.

Đối với việc sử dụng varargs phổ biến nhất, chỉ đơn giản là lặp lại các phần tử, điều này không có vấn đề gì (bạn không quan tâm đến kiểu thời gian chạy của mảng), vì vậy điều này là an toàn:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

Tuy nhiên, đối với bất cứ điều gì phụ thuộc vào loại thành phần thời gian chạy của mảng đã truyền, nó sẽ không an toàn. Đây là một ví dụ đơn giản về một cái gì đó không an toàn và gặp sự cố:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

Vấn đề ở đây là chúng tôi phụ thuộc vào loại argssẽ được T[]trả lại T[]. Nhưng thực tế kiểu đối số trong thời gian chạy không phải là một thể hiện của T[].

3) Nếu phương thức của bạn có một đối số kiểu T...(trong đó T là bất kỳ tham số loại nào), thì:

  • An toàn: Nếu phương thức của bạn chỉ phụ thuộc vào thực tế là các phần tử của mảng là các thể hiện của T
  • Không an toàn: Nếu nó phụ thuộc vào thực tế là mảng là một thể hiện của T[]

Những thứ phụ thuộc vào kiểu thời gian chạy của mảng bao gồm: trả về dưới dạng kiểu T[], chuyển nó làm đối số cho tham số kiểu T[], lấy kiểu mảng bằng cách sử dụng .getClass(), chuyển nó sang các phương thức phụ thuộc vào kiểu thời gian chạy của mảng, như List.toArray()Arrays.copyOf(), Vân vân.

2) Sự khác biệt tôi đã đề cập ở trên là quá phức tạp để có thể dễ dàng phân biệt tự động.


7
tốt bây giờ tất cả có ý nghĩa. cảm ơn. Vì vậy, chỉ để thấy rằng tôi hiểu bạn hoàn toàn, giả sử chúng ta có một lớp FOO<T extends Something>sẽ an toàn khi trả về T [] của phương thức varargs dưới dạng một mảng của Somthings?
Oren

30
Có thể đáng lưu ý rằng một trường hợp (có lẽ) luôn luôn đúng khi sử dụng @SafeVarargslà khi điều duy nhất bạn làm với mảng là chuyển nó sang một phương thức khác đã được chú thích (ví dụ: Tôi thường thấy mình viết các phương thức vararg tồn tại để chuyển đổi đối số của họ sang một danh sách bằng cách sử dụng Arrays.asList(...)và chuyển nó sang phương thức khác, trường hợp như vậy luôn có thể được chú thích @SafeVarargsbởi vì Arrays.asListcó chú thích).
Jules

3
@newacct Tôi không biết đây có phải là trường hợp trong Java 7 không, nhưng Java 8 không cho phép @SafeVarargstrừ khi phương thức này là statichoặc final, vì nếu không, nó có thể bị ghi đè trong lớp dẫn xuất với thứ gì đó không an toàn.
Andy

3
và trong Java 9, nó cũng sẽ được phép cho privatecác phương thức cũng không thể bị ghi đè.
Dave L.

4

Đối với thực tiễn tốt nhất, xem xét điều này.

Nếu bạn có điều này:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Thay đổi nó thành này:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

Tôi đã tìm thấy tôi thường chỉ thêm varargs để thuận tiện hơn cho người gọi. Nó hầu như sẽ luôn thuận tiện hơn cho việc triển khai nội bộ của tôi để sử dụng a List<>. Vì vậy, để cõng Arrays.asList()và đảm bảo không có cách nào tôi có thể giới thiệu Ô nhiễm Heap, đây là những gì tôi làm.

Tôi biết điều này chỉ trả lời # 3 của bạn. newacct đã đưa ra một câu trả lời tuyệt vời cho # 1 và # 2 ở trên, và tôi không có đủ danh tiếng để chỉ để lại nhận xét này. : P

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.