Tại sao một số người cho rằng việc triển khai các generic của Java là không tốt?


115

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?


23
Tôi thấy không có lý do gì để đóng nó; với tất cả những điều tào lao xoay quanh về generic, việc làm rõ một số điểm khác biệt giữa Java và C # có thể giúp mọi người cố gắng né tránh những sai lầm thiếu thông tin.
Rob

Câu trả lời:


146

Xấu:

  • Thông tin loại bị mất trong thời gian biên dịch, vì vậy tại thời điểm thực thi, bạn không thể biết loại nó "có nghĩa là" là gì
  • Không thể được sử dụng cho các loại giá trị (đây là một vấn đề lớn - trong .NET , ví dụ: List<byte>thực sự được hỗ trợ bởi một byte[]ví dụ và không cần quyền anh)
  • Cú pháp để gọi các phương thức chung chung là tệ (IMO)
  • Cú pháp cho các ràng buộc có thể khó hiểu
  • Ký tự đại diện thường khó hiểu
  • Các hạn chế khác nhau do những điều trên - đúc, v.v.

Tốt:

  • Ký tự đại diện cho phép xác định hiệp phương sai / tương phản ở phía gọi, rất gọn gàng trong nhiều tình huống
  • Có còn hơn không!

51
@Paul: Tôi nghĩ rằng tôi sẽ thực sự thích sự cố tạm thời nhưng một API tốt sau đó. Hoặc sử dụng tuyến đường .NET và giới thiệu các loại bộ sưu tập mới. (.NET generics trong 2.0 không phá vỡ ứng dụng 1.1.)
Jon Skeet

6
Với một cơ sở mã lớn hiện có, tôi thích thực tế là tôi có thể bắt đầu thay đổi chữ ký của một phương pháp để sử dụng generic mà không cần thay đổi bất kỳ ai gọi nó.
Paul Tomblin 07/02/09

38
Nhưng mặt trái của nó là rất lớn và vĩnh viễn. Có một chiến thắng lớn trong ngắn hạn và một IMO lỗ dài hạn.
Jon Skeet

11
Jon - "Đó là còn hơn không" là chủ quan :)
MetroidFan2002

6
@Rogerio: Bạn đã xác nhận mặt hàng "Xấu" đầu tiên của tôi là không đúng. Mục "Xấu" đầu tiên của tôi nói rằng thông tin bị mất tại thời điểm biên dịch. Đối với điều đó là không chính xác, bạn phải nói rằng thông tin không bị mất tại thời điểm biên dịch ... Đối với việc gây nhầm lẫn cho các nhà phát triển khác, bạn dường như là người duy nhất bối rối trước những gì tôi đang nói.
Jon Skeet

26

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.


Đó không thực sự là một vấn đề, nó không bao giờ được tạo ra để dành cho thời gian chạy. Nó như thể bạn nói rằng thuyền là một vấn đề vì chúng không thể leo núi. Là một ngôn ngữ, Java thực hiện điều mà nhiều người cần: diễn đạt các kiểu theo cách có ý nghĩa. Đối với phép thuật kiểu thời gian chạy, mọi người vẫn có thể chuyển Classcác đối tượng xung quanh.
Vincent Cantin

1
Anh ấy đúng. công nghệ của bạn là sai vì những người thiết kế con thuyền NÊN đã thiết kế nó để leo núi. Mục tiêu thiết kế của họ không được nghĩ ra cho những gì cần thiết. Bây giờ tất cả chúng ta đều mắc kẹt chỉ với một chiếc thuyền và chúng ta không thể leo núi.
WiegleyJ

1
@VincentCantin - đó chắc chắn là một vấn đề. Do đó, tất cả chúng tôi đều phàn nàn về nó. Các generic của Java vẫn chưa hoàn thiện.
Josh M.

17

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


2
Tôi không chắc tại sao bạn lại bị bỏ phiếu. Từ những gì tôi hiểu bạn đã đúng, và liên kết đó rất giàu thông tin. Đã bình chọn.
Jason Jackson

@Jason, cảm ơn. Có một lỗi đánh máy trong phiên bản gốc của tôi, tôi đoán rằng tôi đã bị loại bỏ vì điều đó.
JaredPar

3
Ồ, bởi vì bạn có thể sử dụng sự phản chiếu để xem một kiểu chung, một chữ ký phương pháp chung. Bạn chỉ không thể sử dụng phản chiếu để kiểm tra thông tin chung được liên kết với một phiên bản được tham số hóa. Ví dụ: nếu tôi có một lớp có phương thức foo (Danh sách <Chuỗi>), tôi có thể tìm thấy "Chuỗi"
oxbow_lakes

Có rất nhiều bài báo nói rằng việc xóa kiểu làm cho việc tìm kiếm các tham số kiểu thực không thể thực hiện được. Giả sử: Đối tượng x = new X [Y] (); bạn có thể chỉ ra làm thế nào, cho x, để tìm loại Y?
user48956

@oxbow_lakes - việc phải sử dụng phản chiếu để tìm một loại chung chung là điều tồi tệ khi không thể làm điều đó. Ví dụ, đó là một giải pháp tồi.
Josh M.

14
  1. Thực hiện thời gian chạy (nghĩa là không xóa kiểu);
  2. Khả năng sử dụng các kiểu nguyên thủy (điều này liên quan đến (1));
  3. Mặc dù ký tự đại diện hữu ích nhưng cú pháp và biết khi nào nên sử dụng nó là điều khiến nhiều người băn khoăn. và
  4. Không cải thiện hiệu suất (vì (1); Java generics là đường cú pháp cho các đối tượng castingi).

(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.


Nhưng trình biên dịch sẽ cho bạn biết, đặc biệt là với tùy chọn rawtypes lint.
Tom Hawtin - tackline

Xin lỗi, tại sao dòng cuối cùng là lỗi biên dịch? Tôi đang loay hoay với nó trong Eclipse và không thể khiến nó bị lỗi ở đó - nếu tôi thêm đủ thứ để phần còn lại của nó biên dịch.
oconnor0

public class Redundancy<R extends Redundancy<R>>;)
java.is.for.desktop

@ oconnor0 Nó không phải là một thất bại biên soạn, nhưng một cảnh báo trình biên dịch, không có lý do rõ ràng:The expression of type List needs unchecked conversion to conform to List<String>
artbristol

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 Enumvà 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.
JAB

10

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.


15
Xin lỗi, tôi không đồng ý với tất cả những gì bạn nói. Nhưng tôi sẽ không bỏ phiếu vì bạn tranh luận nó tốt.
Paul Tomblin 07/02/09

2
Tất nhiên, có rất nhiều ví dụ về các hệ thống lớn, thành công được xây dựng bằng các ngôn ngữ như Python và Ruby thực hiện chính xác những gì tôi đã đề xuất trong câu trả lời của mình.
Clint Miller

Tôi nghĩ rằng toàn bộ sự phản đối của tôi đối với loại ý tưởng này không phải là một ý tưởng tồi, mà là vì các ngôn ngữ hiện đại như Python và Ruby làm cho cuộc sống ngày càng dễ dàng hơn cho các nhà phát triển, các nhà phát triển sẽ tự mãn về mặt trí tuệ và cuối cùng có hiểu mã riêng của họ.
Dan Tao

4
"Và bạn thực sự mua gì cho tất cả những chi tiết thừa đó? ..." Nó cho người lập trình viên bảo trì kém cỏi biết những gì được cho là có trong những bộ sưu tập đó. Tôi nhận thấy đây là một vấn đề khi làm việc với các ứng dụng Java thương mại.
richj

4
"Đã bao nhiêu lần bạn thực sự gặp sự cố khi ai đó đặt dấu [x] vào bộ sưu tập được cho là chứa [y] s?" - Ôi trời, tôi mất tính rồi! Ngoài ra, ngay cả khi không có lỗi, nó cũng là một kẻ giết người dễ đọc. Và việc quét nhiều tệp để tìm ra đối tượng sẽ là gì (hoặc đang gỡ lỗi) thực sự kéo tôi ra khỏi vùng. Bạn có thể thích Haskell - ngay cả khi gõ mạnh nhưng ít rắc rối hơn (vì các kiểu được suy ra).
Martin Capodici

8

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 ++


6

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");

5

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 đề:

  • Loại Erasure (không có thời gian chạy)
  • Không hỗ trợ các loại nguyên thủy
  • Không tương thích với Chú thích (cả hai đều được thêm vào 1.5, tôi vẫn không rõ tại sao chú thích không cho phép các thông số chung ngoài việc gấp rút các tính năng)
  • Không tương thích với Mảng. (Đôi khi tôi thực sự muốn làm điều gì đó giống như Lớp học <? Mở rộng MyObject >[], nhưng tôi không được phép)
  • Cú pháp và hành vi ký tự đại diện Wierd
  • Thực tế là hỗ trợ chung không nhất quán giữa các lớp Java. Họ đã thêm nó vào hầu hết các phương thức của bộ sưu tập, nhưng thỉnh thoảng, bạn lại gặp phải một thể hiện mà nó không có ở đó.

5

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ồ?


3
Suy luận không nhất quán của javac là tào lao.
mP.

3
Tôi giả định rằng lý do mà biểu mẫu thứ hai bị từ chối là vì có thể có nhiều hơn một phiên bản "foo" do quá tải phương thức.
Jason Creighton

2
phê bình này không còn áp dụng nữa vì Java 8 đã giới thiệu suy luận loại mục tiêu được cải thiện
oldrinb

4

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.


3

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.


1
Trình biên dịch sẽ cho bạn biết rằng bạn đã gặp khó khăn nếu bạn quản lý được điều đó.
Tom Hawtin - tackline

@Tom - không nhất thiết. Có nhiều cách để đánh lừa trình biên dịch.
Paul Tomblin

2
Bạn không nghe những gì trình biên dịch nói với bạn ?? (xem bình luận đầu tiên của tôi)
Tom Hawtin - tackline

1
@Tom, nếu bạn có ArrayList <Chuỗi> và bạn chuyển nó vào một phương thức kiểu cũ (giả sử, mã kế thừa hoặc mã của bên thứ ba) có một Danh sách đơn giản, thì phương thức đó có thể thêm bất kỳ thứ gì nó thích vào Danh sách đó. Nếu nó được thực thi trong thời gian chạy, bạn sẽ nhận được một ngoại lệ.
Paul Tomblin

1
static void addToList (Danh sách danh sách) {list.add (1); } List <Chuỗi> list = new ArrayList <Chuỗi> (); addToList (danh sách); Nó biên dịch và thậm chí hoạt động trong thời gian chạy. Lần duy nhất bạn gặp sự cố là khi bạn xóa khỏi danh sách mong đợi một chuỗi và nhận một số nguyên.
Laplie Anderson

3

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 .


2

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.

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.