1) Có nhiều ví dụ trên Internet và trên StackOverflow về vấn đề cụ thể với generics và varargs. Về cơ bản, đó là khi bạn có một số biến của tham số của một loại kiểu tham số:
void foo(T... args);
Trong Java, varargs là một đường cú pháp mà phải trải qua một đơn giản "lại viết" tại thời gian biên dịch: một tham số varargs loại X...
được chuyển đổi thành thông số loại X[]
; và mỗi khi một cuộc gọi được thực hiện cho phương pháp varargs này, trình biên dịch thu thập tất cả các "đối số biến" đi vào tham số varargs và tạo một mảng giống như new X[] { ...(arguments go here)... }
.
Điều này hoạt động tốt khi loại chênh lệch bê tông như String...
. Khi đó là biến kiểu như T...
, nó cũng hoạt động khi T
được biết là loại cụ thể cho cuộc gọi đó. ví dụ. nếu phương pháp trên là một phần của một lớp học Foo<T>
và bạn có tham chiếu Foo<String>
, thì gọi số foo
trên đó sẽ không sao vì chúng tôi biết T
là String
tại thời điểm đó trong mã.
Tuy nhiên, nó không hoạt động khi "giá trị" của T
là một thông số loại khác. Trong Java, không thể tạo một mảng kiểu thành phần kiểu tham số (new T[] { ... }
). Vì vậy, Java thay vì sử dụng new Object[] { ... }
(ở đây Object
là 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 nhau, nó sẽ là thay vì Object
), và sau đó cung cấp cho bạn một cảnh báo trình biên dịch.
Vì vậy, điều gì sai khi tạo new Object[]
thay vì new T[]
hoặc bất kỳ điều gì? Vâng, các mảng trong Java biết kiểu thành phần của chúng khi chạy. Do đó, đối tượng mảng được truyền sẽ có kiểu thành phần sai khi chạy.
Đối lẽ việc sử dụng phổ biến nhất của varargs, chỉ đơn giản là để lặp qua các yếu tố, điều này là không có vấn đề (bạn không quan tâm về loại thời gian chạy của mảng), vì vậy đây là an toàn:
@SafeVarargs
final 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 được truyền, nó sẽ không an toàn.Dưới đây là một ví dụ đơn giản của một cái gì đó là không an toàn và bị treo:
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 ta phụ thuộc vào loại args
là T[]
để trở lại nó như T[]
. Nhưng thực sự loại đố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 pháp của bạn có một đối số kiểu T...
(trong đó T là bất kỳ tham số type), sau đó:
- an toàn: Nếu phương pháp của bạn chỉ phụ thuộc vào thực tế là các yếu tố của mảng là trường hợp 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 điều mà phụ thuộc vào loại thời gian chạy của mảng bao gồm: trở về nó như loại T[]
, đi qua nó như là một cuộc tranh cãi với một tham số kiểu T[]
, nhận kiểu mảng sử dụng .getClass()
, đi qua nó để phương pháp mà phụ thuộc vào loại thời gian chạy của các mảng, như List.toArray()
và Arrays.copyOf()
vv
2) Sự khác biệt Tôi đã đề cập ở trên là quá phức tạp để dễ dàng phân biệt tự động.
Bạn đã xem ví dụ (và giải thích) trong [JavaDoc] (http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html) chưa? – jlordo
Đố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à những người khác: 'retType myMethod (Arg đầu tiên, Arg ... những người khác)'. Nếu bạn không chỉ định 'first', một mảng trống được cho phép và bạn có thể có một phương thức có cùng tên với cùng kiểu trả về không tham số, có nghĩa là JVM sẽ có một thời gian khó xác định phương thức nào gọi là. – fge
@jlordo Tôi đã làm, nhưng tôi không hiểu tại sao nó được đưa ra trong ngữ cảnh varargs vì người ta có thể dễ dàng thay thế tình trạng này bên ngoài một hàm varargs (đã xác minh điều này, biên dịch với cảnh báo an toàn loại và các lỗi trên thời gian chạy) .. – Oren