Tôi đã thỉnh thoảng nghe nói rằng với generic, Java đã không hiểu đúng. (tham khảo gần nhất, tại đây )
Xin lỗi vì sự thiếu kinh nghiệm của tôi, nhưng điều gì sẽ làm cho chúng tốt hơn?
Tôi đã thỉnh thoảng nghe nói rằng với generic, Java đã không hiểu đúng. (tham khảo gần nhất, tại đây )
Xin lỗi vì sự thiếu kinh nghiệm của tôi, nhưng điều gì sẽ làm cho chúng tốt hơn?
Câu trả lời:
Xấu:
List<byte>
thực sự được hỗ trợ bởi một byte[]
ví dụ và không cần quyền anh)Tốt:
Vấn đề lớn nhất là các tổng thể Java là thứ duy nhất trong thời gian biên dịch và bạn có thể lật ngược nó trong thời gian chạy. C # được khen ngợi vì nó kiểm tra thời gian chạy nhiều hơn. Có một số thảo luận thực sự tốt trong bài đăng này và nó liên kết đến các cuộc thảo luận khác.
Class
các đối tượng xung quanh.
Vấn đề chính là Java không thực sự có các số liệu chung trong thời gian chạy. Đó là một tính năng thời gian biên dịch.
Khi bạn tạo một lớp chung trong Java, họ sử dụng một phương thức gọi là "Type Erasure" để thực sự loại bỏ tất cả các kiểu chung khỏi lớp và về cơ bản thay thế chúng bằng Object. Phiên bản cao cấp của generics là trình biên dịch chỉ cần chèn các phôi vào kiểu generic được chỉ định bất cứ khi nào nó xuất hiện trong thân phương thức.
Điều này có rất nhiều mặt trái. Một trong những điểm lớn nhất, IMHO, là bạn không thể sử dụng phản xạ để kiểm tra một loại chung chung. Các loại không thực sự chung chung trong mã byte và do đó không thể được kiểm tra như là loại chung.
Tổng quan tuyệt vời về sự khác biệt ở đây: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) dẫn đến một số hành vi rất kỳ lạ. Ví dụ tốt nhất mà tôi có thể nghĩ đến là. Giả định:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
sau đó khai báo hai biến:
MyClass<T> m1 = ...
MyClass m2 = ...
Bây giờ hãy gọi getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Thứ hai có đối số kiểu chung của nó bị trình biên dịch loại bỏ vì nó là kiểu thô (có nghĩa là kiểu được tham số hóa không được cung cấp) mặc dù nó không liên quan gì đến kiểu được tham số hóa.
Tôi cũng sẽ đề cập đến tuyên bố yêu thích của tôi từ JDK:
public class Enum<T extends Enum<T>>
Ngoài ký tự đại diện (là một túi hỗn hợp), tôi chỉ nghĩ rằng các generic .Net tốt hơn.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
Thoạt đầu có vẻ kỳ lạ / thừa thãi nhưng thực sự khá thú vị, ít nhất là trong những hạn chế của Java / nó là generics. Enum có một values()
phương thức tĩnh đưa ra một mảng các phần tử của chúng được nhập là enum, không phải Enum
và kiểu đó được xác định bởi tham số chung, nghĩa là bạn muốn Enum<T>
. Tất nhiên, cách nhập đó chỉ có ý nghĩa trong ngữ cảnh của một kiểu liệt kê và tất cả các enums đều là lớp con của bạn Enum
, do đó bạn muốn Enum<T extends Enum>
. Tuy nhiên, Java không thích trộn các kiểu raw với generic, do đó Enum<T extends Enum<T>>
để tạo sự nhất quán.
Tôi sẽ đưa ra một ý kiến thực sự gây tranh cãi. Generics làm phức tạp ngôn ngữ và phức tạp mã. Ví dụ, giả sử rằng tôi có một bản đồ ánh xạ một chuỗi với một danh sách các chuỗi. Ngày xưa, tôi có thể tuyên bố điều này đơn giản là
Map someMap;
Bây giờ, tôi phải khai báo nó là
Map<String, List<String>> someMap;
Và mỗi khi tôi chuyển nó vào một số phương thức, tôi phải lặp lại khai báo dài lớn đó một lần nữa. Theo ý kiến của tôi, tất cả những việc gõ thêm đó sẽ khiến nhà phát triển mất tập trung và đưa anh ta ra khỏi "khu vực". Ngoài ra, khi mã chứa rất nhiều mấu chốt, đôi khi thật khó để quay lại sau đó và nhanh chóng sàng lọc tất cả các đoạn mã để tìm ra logic quan trọng.
Java vốn đã có tiếng xấu là một trong những ngôn ngữ dài dòng nhất được sử dụng phổ biến và các ngôn ngữ chung chỉ thêm vào vấn đề đó.
Và bạn thực sự mua những gì cho tất cả những chi tiết đó? Đã bao nhiêu lần bạn thực sự gặp sự cố khi ai đó đặt một Số nguyên vào một tập hợp được cho là giữ Chuỗi hoặc nơi ai đó cố gắng kéo một Chuỗi ra khỏi tập hợp các Số nguyên? Trong 10 năm kinh nghiệm xây dựng các ứng dụng Java thương mại của tôi, đây chưa bao giờ là một nguồn lỗi lớn. Vì vậy, tôi không thực sự chắc chắn những gì bạn nhận được để có thêm chi tiết. Nó thực sự chỉ tấn công tôi như một hành lý bổ sung quan liêu.
Bây giờ tôi sẽ thực sự gây tranh cãi. Điều tôi thấy là vấn đề lớn nhất với các bộ sưu tập trong Java 1.4 là sự cần thiết phải đánh máy ở mọi nơi. Tôi xem những typecast đó là những thứ bổ sung, dài dòng có nhiều vấn đề giống như generic. Vì vậy, ví dụ, tôi không thể chỉ làm
List someList = someMap.get("some key");
tôi phải làm
List someList = (List) someMap.get("some key");
Tất nhiên, lý do là get () trả về một Đối tượng là một siêu kiểu của Danh sách. Vì vậy, bài tập không thể được thực hiện nếu không có dấu chuẩn. Một lần nữa, hãy nghĩ xem quy tắc đó thực sự mua được bạn bao nhiêu. Từ kinh nghiệm của tôi, không nhiều.
Tôi nghĩ rằng Java sẽ tốt hơn nếu 1) nó không thêm generic nhưng 2) thay vào đó đã cho phép truyền ngầm từ supertype sang subtype. Để phôi không chính xác bị bắt trong thời gian chạy. Sau đó, tôi có thể có sự đơn giản của việc xác định
Map someMap;
và sau này làm
List someList = someMap.get("some key");
tất cả mọi thứ sẽ không còn nữa, và tôi thực sự không nghĩ rằng mình sẽ đưa một nguồn lỗi mới lớn vào mã của mình.
Một tác dụng phụ khác của chúng là thời gian biên dịch chứ không phải thời gian chạy là bạn không thể gọi hàm tạo của kiểu chung. Vì vậy, bạn không thể sử dụng chúng để triển khai một nhà máy chung chung ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Các generic của Java được kiểm tra tính đúng đắn tại thời điểm biên dịch và sau đó tất cả thông tin kiểu sẽ bị xóa (quá trình này được gọi là xóa kiểu . Do đó, generic List<Integer>
sẽ được giảm xuống kiểu thô , không phải generic List
, có thể chứa các đối tượng thuộc lớp tùy ý).
Điều này dẫn đến việc có thể chèn các đối tượng tùy ý vào danh sách trong thời gian chạy, cũng như bây giờ không thể biết loại nào được sử dụng làm tham số chung. Sau đó lần lượt dẫn đến
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Tôi ước đây là một wiki để tôi có thể thêm vào những người khác ... nhưng ...
Các vấn đề:
<
? Mở rộng MyObject >
[], nhưng tôi không được phép)Bỏ qua mớ hỗn độn tẩy xóa toàn bộ, các generic như được chỉ định sẽ không hoạt động.
Điều này biên dịch:
List<Integer> x = Collections.emptyList();
Nhưng đây là một lỗi cú pháp:
foo(Collections.emptyList());
Trong đó foo được định nghĩa là:
void foo(List<Integer> x) { /* method body not important */ }
Vì vậy, việc một kiểu biểu thức có kiểm tra hay không phụ thuộc vào việc nó đang được gán cho một biến cục bộ hay một tham số thực sự của một lời gọi phương thức. Thật là điên rồ?
Việc đưa các generic vào Java là một nhiệm vụ khó khăn vì các kiến trúc sư đang cố gắng cân bằng giữa chức năng, tính dễ sử dụng và khả năng tương thích ngược với mã kế thừa. Khá mong đợi, các thỏa hiệp đã phải được thực hiện.
Có một số người cũng cảm thấy rằng việc triển khai các generic của Java đã làm tăng độ phức tạp của ngôn ngữ lên mức không thể chấp nhận được (xem " Generics được coi là có hại " của Ken Arnold ). Câu hỏi thường gặp về Generics của Angelika Langer đưa ra một ý tưởng khá hay về việc mọi thứ có thể trở nên phức tạp như thế nào.
Java không thực thi Generics tại thời điểm chạy, chỉ áp dụng vào thời gian biên dịch.
Điều này có nghĩa là bạn có thể làm những việc thú vị như thêm các loại sai vào Bộ sưu tập chung.
Các mã chung của Java chỉ mang tính thời gian biên dịch và được biên dịch thành mã không chung chung. Trong C #, MSIL được biên dịch thực tế là chung. Điều này có ý nghĩa rất lớn đối với hiệu suất vì Java vẫn truyền trong thời gian chạy. Xem ở đây để biết thêm .
Nếu bạn nghe Java Posse # 279 - Phỏng vấn Joe Darcy và Alex Buckley , họ nói về vấn đề này. Điều đó cũng liên kết đến một bài đăng trên blog của Neal Gafter có tiêu đề Reified Generics for Java nói rằng:
Nhiều người không hài lòng với những hạn chế do cách triển khai generic trong Java. Cụ thể, họ không hài lòng khi các tham số kiểu chung không được sửa đổi: chúng không khả dụng trong thời gian chạy. Generic được thực hiện bằng cách sử dụng tính năng xóa, trong đó các tham số kiểu chung được xóa đơn giản trong thời gian chạy.
Bài đăng trên blog đó, tham chiếu đến một mục cũ hơn, phần Puzzle Through Erasure: answer , nhấn mạnh điểm về khả năng tương thích di chuyển trong các yêu cầu.
Mục tiêu là cung cấp khả năng tương thích ngược của cả mã nguồn và mã đối tượng, cũng như khả năng tương thích di chuyển.