Điều gì là sai với khái quát của Java? [đóng cửa]


49

Tôi đã thấy nhiều lần trên trang này đăng các bài viết về việc triển khai các khái niệm chung của Java. Bây giờ, tôi có thể thành thật nói rằng tôi không có vấn đề gì với việc sử dụng chúng. Tuy nhiên, tôi đã không cố gắng tự tạo một lớp chung chung. Vậy, vấn đề của bạn với hỗ trợ chung của Java là gì?




như Clinton Begin ("anh chàng iBatis") đã nói, "họ không làm việc ..."
gnat

Câu trả lời:


54

Việc triển khai chung của Java sử dụng kiểu xóa . Điều này có nghĩa là các bộ sưu tập chung được gõ mạnh của bạn thực sự thuộc loại Objecttrong thời gian chạy. Điều này có một số cân nhắc về hiệu suất vì nó có nghĩa là các kiểu nguyên thủy phải được đóng hộp khi thêm vào bộ sưu tập chung. Tất nhiên những lợi ích của tính chính xác của kiểu thời gian biên dịch lớn hơn mức độ thông thường của việc xóa kiểu và tập trung ám ảnh vào khả năng tương thích ngược.


4
Chỉ cần thêm, vì xóa kiểu, việc truy xuất thông tin chung trong thời gian chạy là không thể trừ khi bạn sử dụng một cái gì đó như TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/ tựa
Jeremy Heiler

43
Có nghĩa lànew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Lưu ý đến bản thân - nghĩ về một cái tên

9
@Job không có. Và C ++ sử dụng một cách tiếp cận hoàn toàn khác nhau để siêu lập trình được gọi là các mẫu. Nó sử dụng kiểu gõ vịt và bạn có thể thực hiện những việc hữu ích như thế template<T> T add(T v1, T v2) { return v1->add(v2); }và ở đó bạn có một cách thực sự chung để tạo ra một hàm đó addlà hai thứ bất kể những thứ đó là gì, chúng chỉ cần có một phương thức được đặt tên add()với một tham số.
Trinidad

7
@Job nó được tạo mã, thực sự. Các mẫu có nghĩa là để thay thế bộ tiền xử lý C và để làm như vậy, chúng có thể thực hiện một số thủ thuật rất thông minh và bản thân chúng là ngôn ngữ hoàn chỉnh Turing. Generics Java chỉ là đường cho các thùng chứa loại an toàn và generic C # tốt hơn nhưng vẫn là mẫu C ++ của một người nghèo.
Trinidad

3
@Job AFAIK, Java generic không tạo ra một lớp cho mỗi loại chung chung mới, nó chỉ thêm các kiểu chữ vào mã sử dụng các phương thức chung, vì vậy nó không thực sự là IMO lập trình meta. Các mẫu C # tạo mã mới cho từng loại chung, nghĩa là, nếu bạn sử dụng Danh sách <int> và Danh sách <double> trong thế giới của C # sẽ tạo mã cho từng loại. So với các mẫu, chung chung C # vẫn yêu cầu phải biết loại nào bạn có thể cung cấp loại Bạn không thể thực hiện Addví dụ đơn giản mà tôi đã đưa ra, không có cách nào mà không biết trước nó có thể áp dụng lớp nào, đó là nhược điểm.
Trinidad

26

Quan sát rằng các câu trả lời đã được cung cấp tập trung vào sự kết hợp giữa ngôn ngữ Java, JVM và Thư viện lớp Java.

Không có gì sai với các khái quát của Java khi có liên quan đến ngôn ngữ Java. Như được mô tả trong C # vs Java generic , generic của Java khá tốt ở cấp độ ngôn ngữ 1 .

Điều tối ưu là JVM không hỗ trợ trực tiếp chung chung, điều này gây ra một số hậu quả lớn:

  • các phần liên quan đến sự phản chiếu của Thư viện lớp không thể hiển thị thông tin loại đầy đủ có sẵn trong ngôn ngữ Java
  • một số hình phạt hiệu suất có liên quan

Tôi cho rằng sự khác biệt mà tôi đang tạo ra là một ngôn ngữ mô phạm vì ngôn ngữ Java được biên dịch phổ biến với JVM làm thư viện đích và Thư viện lớp Java làm thư viện lõi.

1 Ngoại trừ có thể có các ký tự đại diện, được cho là làm cho kiểu suy luận không thể giải quyết được trong trường hợp chung. Đây là một sự khác biệt lớn giữa các khái niệm chung về C # và Java không được đề cập rất thường xuyên. Cảm ơn, Antimon.


14
JVM không cần hỗ trợ chung chung. Điều này sẽ giống như nói rằng C ++ không có mẫu vì máy thật ix86 không hỗ trợ mẫu, đó là sai. Vấn đề là cách tiếp cận trình biên dịch thực hiện để thực hiện các loại chung. Và một lần nữa, các mẫu C ++ không bị phạt trong thời gian chạy, không có phản ánh nào được thực hiện cả, tôi đoán lý do duy nhất cần được thực hiện trong Java chỉ là thiết kế ngôn ngữ kém.
Trinidad

2
@Trinidad nhưng tôi không nói Java không có chung chung, vì vậy tôi không thấy nó giống nhau như thế nào. Và vâng, JVM không cần hỗ trợ chúng, giống như C ++ không cần tối ưu hóa mã. Nhưng không tối ưu hóa nó chắc chắn sẽ được tính là "một cái gì đó sai".
Roman Starkov

3
Điều tôi lập luận là không cần JVM để có sự hỗ trợ trực tiếp cho các thuốc generic để có thể sử dụng sự phản chiếu trên chúng. Những người khác đã làm điều đó để nó có thể được thực hiện, vấn đề là Java không tạo ra các lớp mới từ khái quát, điều không thể thống nhất.
Trinidad

4
@Trinidad: Trong C ++, giả sử trình biên dịch đã cung cấp đủ thời gian và bộ nhớ, các mẫu có thể được sử dụng để làm bất cứ điều gì và mọi thứ có thể được thực hiện với thông tin có sẵn tại thời gian biên dịch (vì quá trình biên dịch mẫu là hoàn thành Turing), nhưng có không có cách nào để tạo mẫu bằng cách sử dụng thông tin không có sẵn trước khi chương trình được chạy. Ngược lại, trong .net, chương trình có thể tạo các loại dựa trên đầu vào; sẽ khá dễ dàng để viết một chương trình trong đó số lượng các loại khác nhau có thể được tạo ra vượt quá số lượng điện tử trong vũ trụ.
supercat

2
@Trinidad: Rõ ràng, không có sự thực thi duy nhất nào của chương trình có thể tạo ra tất cả các loại đó (hoặc, thực sự, bất cứ thứ gì nằm ngoài một phần vô hạn của chúng), nhưng vấn đề là nó không phải. Nó chỉ cần tạo ra các loại thực sự được sử dụng. Người ta có thể có một chương trình có thể chấp nhận bất kỳ số tùy ý nào (ví dụ 8675309) và từ đó tạo ra một loại duy nhất cho số đó (ví dụ Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>), có các thành viên khác với bất kỳ loại nào khác. Trong C ++, tất cả các loại có thể được tạo từ bất kỳ đầu vào nào sẽ phải được sản xuất tại thời điểm biên dịch.
supercat

13

Những lời chỉ trích thông thường là thiếu sự thống nhất. Điều đó có nghĩa là các đối tượng trong thời gian chạy không chứa thông tin về các đối số chung của chúng (mặc dù thông tin vẫn còn hiện diện trên các trường, phương thức, hàm tạo và lớp và giao diện mở rộng). Điều này có nghĩa là bạn có thể sử dụng, nói, ArrayList<String>để List<File>. Trình biên dịch sẽ đưa ra các cảnh báo cho bạn, nhưng nó cũng sẽ cảnh báo bạn nếu bạn ArrayList<String>gán nó cho một Objecttham chiếu và sau đó chuyển nó tới List<String>. Mặt trái của nó là với các khái quát có lẽ bạn không nên thực hiện, việc thực hiện sẽ tốt hơn nếu không có dữ liệu không cần thiết và tất nhiên là khả năng tương thích ngược.

Một số người phàn nàn rằng bạn không thể quá tải trên cơ sở các đối số chung ( void fn(Set<String>)void fn(Set<File>)). Bạn cần sử dụng tên phương thức tốt hơn để thay thế. Lưu ý rằng quá tải này sẽ không yêu cầu thống nhất, vì quá tải là vấn đề tĩnh, thời gian biên dịch.

Các kiểu nguyên thủy không hoạt động với thuốc generic.

Ký tự đại diện và giới hạn khá phức tạp. Chúng rất hữu ích. Nếu Java ưa thích tính không thay đổi và giao diện không hỏi, thì khai báo bên cạnh sử dụng thay vì tổng quát bên sử dụng sẽ phù hợp hơn.


"Lưu ý rằng quá tải này sẽ không yêu cầu thống nhất, vì quá tải là vấn đề thời gian biên dịch tĩnh." Có phải vậy không? Làm thế nào nó sẽ làm việc mà không thống nhất? JVM sẽ không phải biết trong thời gian chạy mà bạn có loại tập hợp nào, để biết nên gọi phương thức nào?
MatrixFrog

2
@MatrixFrog Không. Như tôi nói, quá tải là vấn đề thời gian biên dịch. Trình biên dịch sử dụng kiểu tĩnh của biểu thức để chọn một quá tải cụ thể, sau đó là phương thức được ghi vào tệp lớp. Nếu bạn có Object cs = new char[] { 'H', 'i' }; System.out.println(cs);bạn nhận được in vô nghĩa. Thay đổi loại csđể char[]và bạn sẽ nhận được Hi.
Tom Hawtin - tackline

10

Generics Java hút vì bạn không thể làm như sau:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

Bạn sẽ không cần phải bán thịt mọi lớp trong đoạn mã trên để làm cho nó hoạt động như bình thường nếu các tổng quát thực sự là các tham số của lớp chung.


Bạn không cần phải bán thịt mỗi lớp. Bạn chỉ cần thêm một lớp nhà máy phù hợp và đưa nó vào AsyncAd CHƯƠNG. (Và về cơ bản, đây không phải là về khái quát, nhưng thực tế là các nhà xây dựng không được kế thừa trong Java).
Peter Taylor

13
@PeterTaylor: Một lớp nhà máy phù hợp chỉ cần đẩy tất cả các nồi hơi vào một số thứ lộn xộn khác và nó vẫn không giải quyết được vấn đề. Bây giờ mỗi khi tôi muốn khởi tạo một thể hiện mới, Parsertôi sẽ cần làm cho mã nhà máy trở nên phức tạp hơn nữa. Trong khi nếu "chung chung" thực sự có nghĩa là chung chung thì sẽ không cần đến các nhà máy và tất cả những điều vô nghĩa khác đi theo tên của các mẫu thiết kế. Đó là một trong nhiều lý do tôi thích làm việc trong các ngôn ngữ động.
davidk01

2
Đôi khi tương tự trong C ++, nơi bạn không thể chuyển địa chỉ của hàm tạo, khi sử dụng các mẫu + ảo - bạn chỉ có thể sử dụng một functor của nhà máy thay thế.
Mark K Cowan

Java tệ bởi vì bạn không thể viết "dự đoán" trước tên của một phương thức? Tội nghiệp bạn. Bám sát ngôn ngữ năng động. Và đặc biệt cảnh giác với Haskell.
fdreger

5
  • cảnh báo trình biên dịch cho các tham số chung bị thiếu không phục vụ mục đích làm cho ngôn ngữ trở nên dài dòng vô nghĩa, ví dụ public String getName(Class<?> klazz){ return klazz.getName();}

  • Generics không chơi đẹp với mảng

  • Thông tin loại bị mất làm cho sự phản chiếu một mớ hỗn độn của băng đúc và ống dẫn.


Tôi cảm thấy khó chịu khi tôi nhận được một cảnh báo cho việc sử dụng HashMapthay vì HashMap<String, String>.
Michael K

1
@Michael nhưng điều đó thực sự nên được sử dụng với thuốc generic ...
thay thế vào

Các vấn đề mảng đi đến reifitable. Xem sách Java Generics (Alligator) để được giải thích.
ncmathsadist

5

Tôi nghĩ rằng những câu trả lời khác đã nói điều này ở một mức độ nào đó, nhưng không rõ ràng lắm. Một trong những vấn đề với thuốc generic là mất các loại chung trong quá trình phản ánh. Ví dụ:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

Thật không may, các kiểu trả về không thể cho bạn biết rằng các kiểu chung của mảng là Chuỗi. Đó là một sự khác biệt tinh tế, nhưng nó quan trọng. Bởi vì mảng được tạo trong thời gian chạy, các kiểu chung bị xóa trong thời gian chạy, do đó bạn không thể tìm ra. Như một số tuyên bố ArrayList<Integer>trông giống như ArrayList<String>từ một quan điểm phản ánh.

Điều này có thể không quan trọng đối với người dùng Generics, nhưng giả sử chúng tôi muốn tạo ra một khung công tác ưa thích sử dụng sự phản chiếu để tìm ra những điều lạ mắt về cách người dùng khai báo các loại chung chung cụ thể.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Giả sử chúng tôi muốn một nhà máy chung tạo ra một thể hiện MySpecialObjectvì đó là loại chung chung cụ thể mà chúng tôi đã khai báo cho trường hợp này. Lớp Factory cũng không thể tự hỏi mình để tìm ra loại cụ thể được khai báo cho trường hợp này vì Java đã xóa chúng.

Trong tổng quát của .Net, bạn có thể thực hiện việc này vì trong thời gian chạy, đối tượng biết các kiểu chung vì trình biên dịch tuân thủ nhị phân. Với việc xóa Java không thể làm điều này.


1

Tôi có thể nói một vài điều tốt về thuốc generic, nhưng đó không phải là câu hỏi. Tôi có thể phàn nàn rằng chúng không có sẵn trong thời gian chạy và không hoạt động với mảng, nhưng điều đó đã được đề cập.

Một phiền toái tâm lý lớn: tôi thỉnh thoảng gặp phải tình huống mà thuốc generic không thể được thực hiện để làm việc. (Mảng là ví dụ đơn giản nhất.) Và tôi không thể biết liệu thuốc generic không thể thực hiện công việc hay liệu tôi chỉ ngu ngốc. Tôi ghét điều đó. Một cái gì đó giống như thuốc generic nên làm việc luôn. Mỗi lần tôi không thể làm những gì tôi muốn bằng ngôn ngữ Java, tôi biết vấn đề là do tôi và tôi biết nếu tôi cứ tiếp tục, cuối cùng tôi sẽ đến đó. Với thuốc generic, nếu tôi trở nên quá cố chấp, tôi có thể lãng phí rất nhiều thời gian.

Nhưng vấn đề thực sự là thuốc generic tạo ra quá nhiều phức tạp cho quá ít lợi ích. Trong những trường hợp đơn giản nhất, nó có thể ngăn tôi thêm một quả táo vào danh sách chứa ô tô. Khỏe. Nhưng nếu không có tướng thì lỗi này sẽ khiến ClassCastException thực sự nhanh chóng trong thời gian chạy với ít thời gian lãng phí. Nếu tôi thêm một chiếc xe có ghế trẻ em có trẻ em trong đó, tôi có cần cảnh báo thời gian biên dịch rằng danh sách này chỉ dành cho những chiếc xe có ghế trẻ em có chứa tinh tinh con không? Một danh sách các đối tượng Object đơn giản bắt đầu trông giống như một ý tưởng tốt.

Mã chung có thể có rất nhiều từ và ký tự và chiếm nhiều không gian thừa và mất nhiều thời gian hơn để đọc. Tôi có thể dành nhiều thời gian để có được tất cả các mã bổ sung đó để hoạt động đúng. Khi nó làm, tôi cảm thấy điên rồ thông minh. Tôi cũng đã lãng phí vài giờ hoặc hơn thời gian của riêng mình và tôi phải tự hỏi liệu có ai khác sẽ có thể tìm ra mã không. Tôi muốn có thể giao nó để bảo trì cho những người kém thông minh hơn tôi hoặc những người có ít thời gian để lãng phí.

Mặt khác (tôi có một chút vết thương ở đó và cảm thấy cần phải cung cấp một số dư), thật tuyệt khi sử dụng các bộ sưu tập và bản đồ đơn giản để thêm, đặt và được kiểm tra khi viết và nó thường không thêm nhiều độ phức tạp của mã ( nếu người khác viết bộ sưu tập hoặc bản đồ) . Và Java tốt hơn ở đây so với C #. Dường như không có bộ sưu tập C # nào tôi muốn sử dụng bao giờ xử lý thuốc generic. (Tôi thừa nhận tôi có sở thích kỳ lạ trong các bộ sưu tập.)


Đẹp rant. Tuy nhiên, có nhiều thư viện / khung có thể tồn tại mà không có chung chung, ví dụ: Guice, Gson, Hibernate. Và thuốc generic không khó lắm khi bạn đã quen với nó. Gõ và đọc các đối số kiểu là một PITA thực sự; ở đây val giúp nếu bạn có thể sử dụng.
maaartinus

1
@maaartinus: Đã viết điều đó một thời gian trước. Ngoài ra OP đã yêu cầu "vấn đề". Tôi thấy thuốc generic cực kỳ hữu ích và thích chúng rất nhiều - bây giờ nhiều hơn thế. Tuy nhiên, nếu bạn đang viết bộ sưu tập của riêng mình, chúng rất khó học. Và tính hữu dụng của chúng mất dần khi loại bộ sưu tập của bạn được xác định trong thời gian chạy - khi bộ sưu tập có một phương thức cho bạn biết lớp của các mục. Tại thời điểm này, thuốc generic không hoạt động và IDE của bạn sẽ tạo ra hàng trăm cảnh báo vô nghĩa. 99,99% lập trình Java không liên quan đến điều này. Nhưng tôi muốn chỉ có một rất nhiều rắc rối với điều này khi tôi đã viết ở trên.
RalphChapin

Là nhà phát triển cả Java và C #, tôi tự hỏi tại sao bạn lại thích các bộ sưu tập Java?
Ivaylo Slavov

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.Điều đó thực sự phụ thuộc vào thời gian chạy xảy ra giữa khởi động chương trình và nhấn vào dòng mã được đề cập. Nếu người dùng báo cáo lỗi và bạn phải mất vài phút (hoặc, trong trường hợp xấu nhất, giờ hoặc thậm chí vài ngày) để sao chép, xác minh thời gian biên dịch đó bắt đầu trông tốt hơn và tốt hơn ...
Mason Wheeler
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.