Lợi ích của tính năng xóa kiểu của Java là gì?


98

Hôm nay tôi đọc một dòng tweet nói rằng:

Thật buồn cười khi người dùng Java phàn nàn về tính năng xóa kiểu, đó là điều duy nhất Java làm đúng, trong khi bỏ qua tất cả những điều nó sai.

Vì vậy, câu hỏi của tôi là:

Có lợi ích nào từ việc xóa kiểu của Java không? Những lợi ích kỹ thuật hoặc phong cách lập trình mà nó (có thể) mang lại ngoài tùy chọn triển khai JVM cho khả năng tương thích ngược và hiệu suất thời gian chạy là gì?


6
Nếu bạn không muốn Câu hỏi của mình bị đóng là "dựa trên ý kiến", bạn nên hỏi nó theo cách khác. (Như "ưu điểm kỹ thuật của loại tẩy là gì"). Tốt hơn hết, hãy hỏi người tweeter xem anh ta đang thực sự nói gì.
Stephen C

3
Loại bỏ "tại sao nó là một điều tốt". "Điều tốt" là một đặc điểm chủ quan thường xuyên, và nó (rõ ràng) đặt ra câu hỏi rằng loại tẩy xóa một điều tốt ... điều đó gây tranh cãi.
Stephen C

3
Xin Chúa giúp chúng tôi nếu trang web này sẽ biến thành những câu hỏi về mọi thứ đã từng được đăng trên Twitter. Ít nhất hãy trích dẫn một nguồn uy tín cho câu hỏi của bạn.
Marquis of Lorne

21
Tại sao câu hỏi cần lấy từ "nguồn uy tín"? Thật là ngớ ngẩn.
vertti

4
Hầu hết các vấn đề ở đây không đến từ BẤT KỲ nguồn nào, ngoại trừ vấn đề của chính người hỏi với phần mềm / ngôn ngữ / cái gì đó cụ thể, và may mắn thay, có rất nhiều chuyên gia "lãng phí" thời gian của họ để giúp những người này.
vertti

Câu trả lời:


90

Loại tẩy là tốt

Hãy bám sát sự thật

Cho đến nay, rất nhiều câu trả lời được người dùng Twitter quan tâm quá mức. Sẽ rất hữu ích nếu bạn tập trung vào tin nhắn chứ không phải người đưa tin. Có một thông điệp khá nhất quán với ngay cả những đoạn trích được đề cập cho đến nay:

Thật buồn cười khi người dùng Java phàn nàn về tính năng xóa kiểu, đó là điều duy nhất Java làm đúng, trong khi bỏ qua tất cả những điều nó sai.

Tôi nhận được những lợi ích khổng lồ (ví dụ như tham số) và chi phí không (chi phí bị cáo buộc là một giới hạn của trí tưởng tượng).

mới T là một chương trình bị hỏng. Nó đồng nghĩa với tuyên bố "tất cả các mệnh đề đều đúng." Tôi không quan tâm đến điều này.

Mục tiêu: các chương trình hợp lý

Những dòng tweet này phản ánh một quan điểm không quan tâm đến việc liệu chúng ta có thể khiến máy móc làm điều gì đó hay không , mà quan tâm hơn đến việc liệu chúng ta có thể lý giải rằng máy móc sẽ làm điều gì đó chúng ta thực sự muốn hay không. Lập luận tốt là một bằng chứng. Bằng chứng có thể được chỉ định trong ký hiệu chính thức hoặc một cái gì đó ít trang trọng hơn. Bất kể ngôn ngữ đặc tả là gì, chúng phải rõ ràng và chặt chẽ. Các đặc tả không chính thức không phải là không thể cấu trúc chính xác, nhưng thường có sai sót trong lập trình thực tế. Chúng tôi kết thúc với các biện pháp khắc phục như kiểm tra tự động và khám phá để bù đắp cho các vấn đề chúng tôi gặp phải với lý luận không chính thức. Điều này không có nghĩa là thử nghiệm về bản chất là một ý tưởng tồi, nhưng người dùng Twitter được trích dẫn đang gợi ý rằng có một cách tốt hơn nhiều.

Vì vậy, mục tiêu của chúng tôi là có các chương trình chính xác mà chúng tôi có thể lập luận một cách rõ ràng và chặt chẽ theo cách tương ứng với cách máy thực thi chương trình. Tuy nhiên, đây không phải là mục tiêu duy nhất. Chúng tôi cũng muốn logic của chúng tôi có một mức độ thể hiện. Ví dụ, chỉ có rất nhiều thứ mà chúng ta có thể diễn đạt bằng logic mệnh đề. Thật tuyệt khi có định lượng phổ quát (∀) và hiện sinh (∃) từ một cái gì đó giống như logic bậc nhất.

Sử dụng hệ thống kiểu để lập luận

Các mục tiêu này có thể được giải quyết rất tốt bởi các hệ thống loại. Điều này đặc biệt rõ ràng vì có thư từ Curry-Howard . Sự tương ứng này thường được thể hiện với sự tương tự sau đây: các loại là chương trình như định lý là chứng minh.

Thư từ này có phần sâu sắc. Chúng ta có thể lấy các biểu thức logic và dịch chúng thông qua sự tương ứng với các loại. Sau đó, nếu chúng ta có một chương trình có cùng kiểu chữ ký để biên dịch, chúng ta đã chứng minh rằng biểu thức logic là đúng phổ biến (một phép cộng). Điều này là do sự tương ứng là hai chiều. Việc chuyển đổi giữa kiểu / chương trình và thế giới định lý / chứng minh là cơ học và trong nhiều trường hợp có thể được tự động hóa.

Curry-Howard chơi một cách tuyệt vời những gì chúng tôi muốn làm với các thông số kỹ thuật cho một chương trình.

Các hệ thống kiểu có hữu ích trong Java không?

Ngay cả khi hiểu rõ về Curry-Howard, một số người vẫn thấy dễ dàng loại bỏ giá trị của một hệ thống kiểu, khi nó

  1. rất khó để làm việc với
  2. tương ứng (thông qua Curry-Howard) với một lôgic có khả năng diễn đạt hạn chế
  3. bị hỏng (được mô tả đặc tính của hệ thống là "yếu" hoặc "mạnh").

Về điểm đầu tiên, có lẽ IDE làm cho hệ thống kiểu của Java đủ dễ dàng để làm việc (điều đó rất chủ quan).

Về điểm thứ hai, Java gần như tương ứng với logic bậc một. Generics cho phép sử dụng hệ thống loại tương đương với định lượng phổ quát. Thật không may, các ký tự đại diện chỉ cung cấp cho chúng ta một phần nhỏ định lượng hiện sinh. Nhưng định lượng phổ quát là một khởi đầu khá tốt. Thật tuyệt khi có thể nói rằng các chức năng List<A>hoạt động phổ biến cho tất cả các danh sách có thể có vì A hoàn toàn không bị giới hạn. Điều này dẫn đến những gì người dùng Twitter đang nói về "tham số".

Một bài báo thường được trích dẫn về tham số là Định lý của Philip Wadler miễn phí! . Điều thú vị về bài báo này là chỉ từ một chữ ký kiểu, chúng ta có thể chứng minh một số bất biến rất thú vị. Nếu chúng tôi viết các bài kiểm tra tự động cho những bất biến này, chúng tôi sẽ rất lãng phí thời gian của mình. Ví dụ, đối với List<A>, chỉ từ chữ ký loại choflatten

<A> List<A> flatten(List<List<A>> nestedLists);

chúng ta có thể lập luận rằng

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Đó là một ví dụ đơn giản và bạn có thể lý luận về nó một cách không chính thức, nhưng thậm chí còn đẹp hơn khi chúng tôi nhận được các bằng chứng như vậy chính thức miễn phí từ hệ thống loại và được kiểm tra bởi trình biên dịch.

Không xóa có thể dẫn đến lạm dụng

Từ góc độ triển khai ngôn ngữ, các chỉ số chung của Java (tương ứng với các kiểu phổ quát) đóng vai trò rất quan trọng trong tham số được sử dụng để có được bằng chứng về những gì chương trình của chúng tôi làm. Đây là vấn đề thứ ba được đề cập. Tất cả những lợi ích về chứng minh và tính đúng đắn này đòi hỏi một hệ thống loại âm thanh được thực hiện không có khiếm khuyết. Java chắc chắn có một số tính năng ngôn ngữ cho phép chúng ta phá vỡ lý luận của mình. Chúng bao gồm nhưng không giới hạn ở:

  • tác dụng phụ với một hệ thống bên ngoài
  • sự phản chiếu

Các yếu tố chung không bị xóa theo nhiều cách liên quan đến sự phản ánh. Không có sự tẩy xóa, có thông tin thời gian chạy được mang theo việc triển khai mà chúng ta có thể sử dụng để thiết kế các thuật toán của mình. Điều này có nghĩa là về mặt tĩnh, khi chúng ta lý luận về các chương trình, chúng ta không có bức tranh đầy đủ. Sự phản ánh đe dọa nghiêm trọng tính đúng đắn của bất kỳ bằng chứng nào mà chúng ta lý luận về mặt tĩnh. Không phải ngẫu nhiên mà sự phản chiếu cũng dẫn đến một loạt các khiếm khuyết phức tạp.

Vì vậy, những cách mà các generic không bị xóa có thể "hữu ích" là gì? Hãy xem xét cách sử dụng được đề cập trong tweet:

<T> T broken { return new T(); }

Điều gì xảy ra nếu T không có hàm tạo no-arg? Trong một số ngôn ngữ, những gì bạn nhận được là rỗng. Hoặc có lẽ bạn bỏ qua giá trị null và chuyển thẳng đến việc nâng cao một ngoại lệ (mà giá trị null dường như vẫn dẫn đến). Bởi vì ngôn ngữ của chúng ta là Turing hoàn chỉnh, nên không thể lý giải về việc lệnh gọi nào brokensẽ liên quan đến các kiểu "an toàn" với các hàm tạo không đối số và những lệnh nào sẽ không. Chúng tôi đã đánh mất sự chắc chắn rằng chương trình của chúng tôi hoạt động trên toàn cầu.

Xóa có nghĩa là chúng tôi đã lý luận (vì vậy hãy xóa)

Vì vậy, nếu chúng tôi muốn lập luận về các chương trình của mình, chúng tôi đặc biệt khuyên không nên sử dụng các đặc điểm ngôn ngữ đe dọa mạnh mẽ đến lý luận của chúng tôi. Một khi chúng tôi làm điều đó, thì tại sao không chỉ bỏ các loại trong thời gian chạy? Chúng không cần thiết. Chúng ta có thể nhận được một số hiệu quả và sự đơn giản với sự hài lòng rằng không có phôi nào bị lỗi hoặc các phương thức có thể bị thiếu khi gọi.

Xóa khuyến khích lý luận.


7
Trong thời gian tôi tập hợp câu trả lời này, một số người khác đã trả lời với câu trả lời tương tự. Nhưng đã viết nhiều thế này, tôi quyết định đăng nó hơn là bỏ đi.
Sukant Hajra

32
Về mặt tôn trọng, tính năng xóa kiểu là một nỗ lực nửa vời để đưa một tính năng vào Java mà không phá vỡ khả năng tương thích ngược. Nó không phải là một thế mạnh. Nếu mối quan tâm chính là bạn có thể cố gắng khởi tạo một đối tượng không có hàm tạo không tham số, thì câu trả lời đúng là cho phép một ràng buộc của "kiểu có hàm tạo không tham số", không làm tê liệt một tính năng ngôn ngữ.
Cơ bản

5
Về cơ bản, với sự tôn trọng, bạn đang không đưa ra một lập luận chặt chẽ. Bạn chỉ đơn thuần khẳng định rằng tẩy xóa đang "làm tê liệt" với việc hoàn toàn không quan tâm đến giá trị của các bản kiểm chứng thời gian biên dịch như một công cụ thuyết phục để lập luận về chương trình. Hơn nữa, những lợi ích này hoàn toàn trực giao của con đường quanh co và động lực mà Java thực hiện để triển khai generic. Ngoài ra, những lợi ích này cũng dành cho các ngôn ngữ có cách triển khai tính năng xóa được khai thác hơn nhiều.
Sukant Hajra

44
Lập luận được trình bày ở đây không phải chống lại việc tẩy xóa, mà là chống lại phản xạ (và kiểm tra kiểu thời gian chạy nói chung) - về cơ bản nó khẳng định rằng phản xạ là xấu, vì vậy thực tế là xóa không hoạt động tốt với chúng là tốt. Đó là một điểm hợp lệ của riêng nó (mặc dù nhiều người sẽ không đồng ý); nhưng nó không có ý nghĩa nhiều trong ngữ cảnh, bởi vì Java đã có kiểm tra phản chiếu / thời gian chạy, và chúng đã không và sẽ không biến mất. Vì vậy, để có một cấu trúc không phù hợp với phần ngôn ngữ hiện có, không bị phản đối đó một hạn chế, bất kể bạn nhìn nó như thế nào.
Pavel Minaev

10
Câu trả lời của bạn chỉ đơn giản là lạc đề. Bạn nhập một giải thích dài dòng (mặc dù thú vị theo các thuật ngữ của riêng nó) về lý do tại sao xóa kiểu như một khái niệm trừu tượng thường hữu ích trong việc thiết kế các hệ thống kiểu, nhưng chủ đề là lợi ích của một đặc tính cụ thể của Java Generics được gắn nhãn "xóa kiểu ", chỉ bề ngoài giống với khái niệm bạn mô tả, thất bại hoàn toàn trong việc cung cấp những bất biến thú vị và đưa ra những ràng buộc không hợp lý.
Marko Topolnik

40

Các kiểu là một cấu trúc được sử dụng để viết chương trình theo cách cho phép trình biên dịch kiểm tra tính đúng đắn của chương trình. Kiểu là một mệnh đề trên một giá trị - trình biên dịch xác minh rằng mệnh đề này là đúng.

Trong quá trình thực thi chương trình, không cần thông tin kiểu - thông tin này đã được trình biên dịch xác nhận. Trình biên dịch sẽ được tự do loại bỏ thông tin này để thực hiện tối ưu hóa mã - làm cho mã chạy nhanh hơn, tạo một tệp nhị phân nhỏ hơn, v.v. Việc xóa các tham số kiểu tạo điều kiện thuận lợi cho việc này.

Java phá vỡ kiểu nhập tĩnh bằng cách cho phép thông tin kiểu được truy vấn trong thời gian chạy - phản ánh, instanceof, v.v. Điều này cho phép bạn xây dựng các chương trình không thể được xác minh tĩnh - chúng bỏ qua hệ thống kiểu. Nó cũng bỏ lỡ cơ hội tối ưu hóa tĩnh.

Thực tế là các tham số kiểu bị xóa sẽ ngăn không cho một số trường hợp của các chương trình không chính xác này được xây dựng, tuy nhiên, các chương trình không chính xác hơn sẽ không được phép nếu xóa nhiều thông tin kiểu hơn và các cơ sở phản chiếu và phiên bản bị xóa.

Xóa rất quan trọng để duy trì thuộc tính "tham số" của một kiểu dữ liệu. Giả sử tôi có một loại "Danh sách" được tham số trên loại thành phần T. tức là Danh sách <T>. Kiểu đó là một mệnh đề mà kiểu Danh sách này hoạt động giống hệt nhau đối với bất kỳ kiểu nào T. Thực tế là T là một tham số kiểu trừu tượng, không bị ràng buộc có nghĩa là chúng ta không biết gì về kiểu này, do đó không được làm bất cứ điều gì đặc biệt đối với các trường hợp đặc biệt của T.

ví dụ: nói rằng tôi có một Danh sách xs = asList ("3"). Tôi thêm một phần tử: xs.add ("q"). Tôi kết thúc bằng ["3", "q"]. Vì đây là tham số, tôi có thể giả sử rằng List xs = asList (7); xs.add (8) kết thúc bằng [7,8] Tôi biết từ loại rằng nó không làm một việc cho String và một việc cho Int.

Hơn nữa, tôi biết rằng hàm List.add không thể phát minh ra các giá trị của T trong không khí loãng. Tôi biết rằng nếu asList của tôi ("3") có thêm "7" vào đó, thì các câu trả lời khả thi duy nhất sẽ được tạo từ các giá trị "3" và "7". Không có khả năng "2" hoặc "z" được thêm vào danh sách vì hàm sẽ không thể xây dựng nó. Không có giá trị nào khác trong số này hợp lý để thêm vào và tham số ngăn không cho các chương trình không chính xác này được xây dựng.

Về cơ bản, tính năng xóa ngăn chặn một số phương tiện vi phạm tham số, do đó loại bỏ khả năng xuất hiện các chương trình không chính xác, đó là mục tiêu của việc nhập tĩnh.


15

(Mặc dù tôi đã viết câu trả lời ở đây, nhưng xem lại câu hỏi này hai năm sau, tôi nhận ra có một cách trả lời khác, hoàn toàn khác, vì vậy tôi để nguyên câu trả lời trước và thêm câu này.)


Rất có thể tranh cãi về việc liệu quá trình được thực hiện trên Java Generics có xứng đáng với cái tên "loại xóa" hay không. Vì các loại chung chung không bị xóa mà được thay thế bằng các đối tác thô của chúng, một lựa chọn tốt hơn có vẻ là "cắt xén kiểu".

Tính năng tinh túy của tính năng xóa kiểu theo nghĩa thường được hiểu của nó là buộc thời gian chạy phải ở trong ranh giới của hệ thống kiểu tĩnh bằng cách làm cho nó "mù" với cấu trúc của dữ liệu mà nó truy cập. Điều này cung cấp toàn bộ sức mạnh cho trình biên dịch và cho phép nó chứng minh các định lý chỉ dựa trên các kiểu tĩnh. Nó cũng giúp lập trình viên bằng cách hạn chế bậc tự do của mã, mang lại nhiều sức mạnh hơn cho suy luận đơn giản.

Tính năng xóa kiểu của Java không đạt được điều đó — nó làm tê liệt trình biên dịch, như trong ví dụ này:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Hai khai báo trên thu gọn thành cùng một chữ ký phương pháp sau khi xóa.)

Mặt khác, thời gian chạy vẫn có thể kiểm tra loại đối tượng và lý do về nó, nhưng vì cái nhìn sâu sắc của nó về loại thực bị làm tê liệt bởi tính năng xóa, vi phạm loại tĩnh là rất nhỏ để đạt được và khó có thể ngăn chặn.

Để làm cho mọi thứ trở nên phức tạp hơn, chữ ký loại gốc và loại bị xóa cùng tồn tại và được xem xét song song trong quá trình biên dịch. Điều này là do toàn bộ quá trình không phải là xóa thông tin kiểu khỏi thời gian chạy, mà là về việc biến một hệ thống kiểu chung thành hệ thống kiểu thô kế thừa để duy trì khả năng tương thích ngược. Đá quý này là một ví dụ cổ điển:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Phần thừa extends Objectphải được thêm vào để duy trì tính tương thích ngược của chữ ký bị xóa.)

Bây giờ, với suy nghĩ đó, chúng ta hãy xem lại trích dẫn:

Thật buồn cười khi người dùng Java phàn nàn về việc xóa kiểu, đó là điều duy nhất mà Java làm đúng

Chính xác thì điều gì đã làm cho Java đúng? Nó có phải là từ chính nó, bất kể nghĩa? Ngược lại, hãy xem intkiểu khiêm tốn : không có kiểm tra kiểu thời gian chạy nào được thực hiện, hoặc thậm chí có thể, và việc thực thi luôn hoàn toàn an toàn về kiểu. Đó là kiểu tẩy xóa trông như thế nào khi được thực hiện đúng: bạn thậm chí không biết nó ở đó.


10

Một điều tôi không thấy ở đây là tính đa hình thời gian chạy của OOP về cơ bản phụ thuộc vào việc sửa đổi các loại trong thời gian chạy. Khi một ngôn ngữ có xương sống được giữ vững bởi các kiểu được chỉnh sửa giới thiệu một phần mở rộng lớn cho hệ thống kiểu của nó và dựa trên việc xóa kiểu, thì sự bất đồng về nhận thức là kết quả không thể tránh khỏi. Đây chính xác là những gì đã xảy ra với cộng đồng Java; đó là lý do tại sao tính năng xóa kiểu đã thu hút rất nhiều tranh cãi, và cuối cùng là tại sao có kế hoạch hoàn tác nó trong một bản phát hành Java trong tương lai . Tìm thấy điều gì đó buồn cười trong lời phàn nàn đó của người dùng Java phản bội hoặc là một sự hiểu lầm trung thực về tinh thần của Java, hoặc một trò đùa có ý thức không được chấp nhận.

Tuyên bố "xóa là điều duy nhất Java có quyền" ngụ ý tuyên bố rằng "tất cả các ngôn ngữ dựa trên điều phối động chống lại kiểu thời gian chạy của đối số hàm về cơ bản là sai sót". Mặc dù chắc chắn là một tuyên bố hợp pháp của riêng nó, và thậm chí có thể được coi là lời chỉ trích hợp lệ đối với tất cả các ngôn ngữ OOP bao gồm cả Java, nó không thể tự coi mình là điểm mấu chốt để đánh giá và chỉ trích các tính năng trong ngữ cảnh của Java , nơi mà tính đa hình thời gian chạy là tiên đề.

Tóm lại, mặc dù người ta có thể tuyên bố hợp lệ "xóa kiểu là cách để đi trong thiết kế ngôn ngữ", các vị trí hỗ trợ xóa kiểu trong Java được đặt sai vị trí đơn giản vì nó đã quá muộn và đã quá muộn ngay cả vào thời điểm lịch sử khi Oak được Sun đón nhận và đổi tên thành Java.



Về việc liệu bản thân gõ tĩnh có phải là định hướng thích hợp trong việc thiết kế ngôn ngữ lập trình hay không, điều này phù hợp với bối cảnh triết học rộng lớn hơn nhiều về những gì chúng ta nghĩ cấu thành hoạt động của lập trình . Một trường phái tư tưởng, bắt nguồn rõ ràng từ truyền thống toán học cổ điển, coi các chương trình là các thể hiện của một khái niệm toán học này hay khái niệm toán học khác (mệnh đề, hàm, v.v.), nhưng có một kiểu tiếp cận hoàn toàn khác, coi lập trình là một cách để nói chuyện với máy và giải thích những gì chúng ta muốn từ nó. Theo quan điểm này, chương trình là một thực thể năng động, đang phát triển có tổ chức, đối lập hoàn toàn với cấu trúc của một chương trình được đánh máy tĩnh.

Có vẻ tự nhiên nếu coi các ngôn ngữ động là một bước đi theo hướng đó: tính nhất quán của chương trình xuất hiện từ dưới lên, không có hằng số tiên nghiệm nào áp đặt nó theo cách từ trên xuống. Mô hình này có thể được coi là một bước hướng tới mô hình hóa quá trình mà theo đó chúng ta, con người, trở thành những gì chúng ta đang có thông qua phát triển và học hỏi.


Cảm ơn vì điều đó. Đó là kiểu hiểu chính xác sâu hơn về các triết lý liên quan mà tôi mong đợi được thấy cho câu hỏi này.
Daniel Langdon

Công văn động không phụ thuộc vào bất kỳ loại nào được sửa đổi. Nó thường dựa vào cái mà tài liệu lập trình gọi là "thẻ", nhưng thậm chí sau đó, nó không cần sử dụng thẻ. Nếu bạn tạo một giao diện (cho một đối tượng) và tạo các phiên bản ẩn danh của giao diện đó, thì bạn đang thực hiện điều phối động mà không cần đến các kiểu sửa đổi.
Sukant Hajra

@sukanthajra Đó chỉ là tách tóc. Thẻ là thông tin thời gian chạy mà bạn cần để giải quyết loại hành vi mà phiên bản thể hiện. Nó nằm ngoài tầm với của hệ thống kiểu tĩnh và đó là bản chất của khái niệm đang được thảo luận.
Marko Topolnik

Tồn tại một kiểu rất tĩnh được thiết kế cho chính xác lý luận này được gọi là "kiểu tổng". Nó cũng được gọi là "kiểu biến thể" (trong cuốn sách Các kiểu và Ngôn ngữ lập trình rất hay của Benjamin Pierce). Vì vậy, đây không phải là tóc chẻ chút nào. Ngoài ra, bạn có thể sử dụng catamorphism / gấp / khách truy cập cho một cách tiếp cận hoàn toàn không có thẻ.
Sukant Hajra

Cảm ơn bạn vì bài lý thuyết, nhưng mình thấy không liên quan. Chủ đề của chúng tôi là hệ thống kiểu của Java.
Marko Topolnik

9

Một bài đăng tiếp theo của cùng một người dùng trong cùng một cuộc trò chuyện:

mới T là một chương trình bị hỏng. Nó đồng nghĩa với tuyên bố "tất cả các mệnh đề đều đúng." Tôi không quan tâm đến điều này.

(Điều này nhằm đáp lại tuyên bố của một người dùng khác, cụ thể là "có vẻ như trong một số trường hợp, 'chữ T' mới sẽ tốt hơn", ý tưởng đó new T()là không thể do xóa kiểu. (Điều này còn gây tranh cãi - ngay cả khi Tcó sẵn tại thời gian chạy, nó có thể là một lớp hoặc giao diện trừu tượng, hoặc nó có thể có Void, hoặc nó có thể thiếu một phương thức khởi tạo no-arg, hoặc phương thức khởi tạo no-arg của nó có thể là private (ví dụ: vì nó được cho là một lớp singleton), hoặc hàm tạo no-arg có thể chỉ định một ngoại lệ đã được kiểm tra mà phương thức chung không bắt hoặc chỉ định - nhưng đó là tiền đề. Bất kể, đúng là nếu không có tẩy xóa thì ít nhất bạn cũng có thể viết T.class.newInstance(), phương thức xử lý những vấn đề đó.))

Quan điểm này cho rằng các kiểu là đẳng lập với các mệnh đề, gợi ý rằng người dùng có kiến ​​thức nền tảng về lý thuyết kiểu chính thức. (S) anh ta rất có thể không thích "kiểu động" hoặc "kiểu thời gian chạy" và sẽ thích một Java không có downcast instanceofvà phản chiếu, v.v. (Hãy nghĩ về một ngôn ngữ như Standard ML, có hệ thống kiểu (tĩnh) rất phong phú và ngữ nghĩa động của nó không phụ thuộc vào bất kỳ thông tin kiểu nào.)

Nhân tiện, cần lưu ý rằng người dùng đang troll: trong khi (các) anh ta có thể chân thành thích các ngôn ngữ được gõ (tĩnh), thì (các) anh ta không chân thành cố thuyết phục người khác về quan điểm đó. Thay vào đó, mục đích chính của tweet ban đầu là để chế nhạo những người không đồng ý, và sau khi một số người không đồng ý với nhau, người dùng đã đăng các dòng tweet tiếp theo như "lý do java có kiểu xóa là Wadler et al biết điều gì họ đang làm, không giống như người dùng java ". Thật không may, điều này khiến bạn khó tìm ra (những) anh ấy thực sự đang nghĩ gì; nhưng may mắn thay, nó cũng có nghĩa là nó không quá quan trọng để làm như vậy. Những người có chiều sâu thực tế về quan điểm của họ thường không dùng đến những trò troll hoàn toàn không có nội dung này.


2
Nó không biên dịch trong Java nó có kiểu tẩy xoá
Đánh dấu Rotteveel

1
@MarkRotteveel: Cảm ơn; Tôi đã thêm một đoạn để giải thích (và bình luận) về điều đó.
ruakh

1
Đánh giá từ sự pha trộn của số phiếu ủng hộ và số phiếu phản đối, đây là câu trả lời gây tranh cãi nhất mà tôi từng đăng. Tôi tự hào lạ lùng.
ruakh

6

Một điều tốt là không cần phải thay đổi JVM khi các thuốc chung được giới thiệu. Java chỉ triển khai các generic ở cấp trình biên dịch.


1
JVM thay đổi so với những gì? Các mẫu cũng sẽ không yêu cầu thay đổi JVM.
Marquis of Lorne,

5

Lý do tại sao loại tẩy xóa là một điều tốt là những thứ mà nó không thể tạo ra lại có hại. Việc ngăn chặn việc kiểm tra các đối số kiểu trong thời gian chạy giúp việc hiểu và lập luận về chương trình dễ dàng hơn.

Một quan sát mà tôi thấy hơi phản trực quan là khi các chữ ký hàm chung chung hơn , chúng trở nên dễ hiểu hơn. Điều này là do số lần triển khai có thể bị giảm. Hãy xem xét một phương pháp với chữ ký này, bằng cách nào đó chúng tôi biết là không có tác dụng phụ:

public List<Integer> XXX(final List<Integer> l);

Các triển khai có thể có của chức năng này là gì? Rất nhiều. Bạn có thể nói rất ít về chức năng này. Nó có thể đảo ngược danh sách đầu vào. Nó có thể là ghép nối các int với nhau, tổng hợp chúng và trả về một danh sách có kích thước bằng một nửa. Có nhiều khả năng khác có thể được tưởng tượng. Bây giờ hãy xem xét:

public <T> List<T> XXX(final List<T> l);

Có bao nhiêu cách triển khai của hàm này? Vì việc triển khai không thể biết loại của các phần tử, một số lượng lớn các triển khai hiện có thể bị loại trừ: các phần tử không thể được kết hợp hoặc thêm vào danh sách hoặc bị lọc ra, v.v. Chúng tôi bị giới hạn ở những thứ như: danh tính (không thay đổi danh sách), bỏ phần tử hoặc đảo ngược danh sách. Chức năng này dễ lý giải hơn chỉ dựa trên chữ ký của nó.

Ngoại trừ… trong Java, bạn luôn có thể gian lận kiểu hệ thống. Bởi vì việc triển khai phương pháp chung đó có thể sử dụng những thứ như instanceofkiểm tra và / hoặc phôi thành các kiểu tùy ý, lý luận của chúng ta dựa trên chữ ký kiểu có thể dễ dàng trở nên vô dụng. Hàm có thể kiểm tra loại của các phần tử và thực hiện bất kỳ số thứ nào dựa trên kết quả. Nếu các hack thời gian chạy này được cho phép, các chữ ký phương thức được tham số hóa trở nên ít hữu ích hơn đối với chúng ta.

Nếu Java không có tính năng xóa kiểu (nghĩa là, các đối số kiểu được sửa lại trong thời gian chạy) thì điều này chỉ đơn giản là cho phép nhiều trò tai quái kiểu này hơn. Trong ví dụ trên, việc triển khai chỉ có thể vi phạm các kỳ vọng được thiết lập bởi chữ ký kiểu nếu danh sách có ít nhất một phần tử; nhưng nếu Tđược sửa đổi, nó có thể làm như vậy ngay cả khi danh sách trống. Các loại được sửa đổi sẽ chỉ làm tăng (đã có rất nhiều) khả năng cản trở sự hiểu biết của chúng ta về mã.

Loại tẩy xóa làm cho ngôn ngữ ít "mạnh mẽ" hơn. Nhưng một số dạng "quyền lực" thực sự có hại.


1
Có vẻ như bạn đang tranh luận rằng các chỉ số chung quá phức tạp đối với các nhà phát triển Java để "hiểu và suy luận" hoặc rằng họ không thể tin tưởng để không tự bắn vào chân mình nếu có cơ hội. Cái thứ hai dường như đồng bộ với đặc tính của Java (ala không có kiểu không dấu, v.v.) nhưng nó có vẻ hơi hạ mình đối với các nhà phát triển
Basic

1
@Basic Tôi phải thể hiện bản thân rất kém, bởi vì đó không phải là ý của tôi. Vấn đề không phải là generic phức tạp, mà là những thứ như đúc và instanceofcản trở khả năng suy luận của chúng ta về những gì mã hoạt động dựa trên các loại. Nếu Java phải sửa đổi các đối số kiểu, nó sẽ chỉ làm cho vấn đề này trở nên tồi tệ hơn. Xóa kiểu trong thời gian chạy có tác dụng làm cho hệ thống kiểu hữu ích hơn.
Lachlan

@lachlan nó hoàn toàn có ý nghĩa đối với tôi và tôi không nghĩ rằng việc đọc nó bình thường sẽ gây ra bất kỳ sự xúc phạm nào đối với các nhà phát triển Java.
Rob Grant

3

Đây không phải là câu trả lời trực tiếp (OP hỏi "lợi ích là gì", tôi trả lời "nhược điểm là gì")

So với hệ thống kiểu C #, việc xóa kiểu Java là một nỗi đau thực sự đối với hai người

Bạn không thể triển khai một giao diện hai lần

Trong C #, bạn có thể triển khai cả hai IEnumerable<T1>IEnumerable<T2>một cách an toàn, đặc biệt nếu hai kiểu không có chung tổ tiên (tức là tổ tiên của chúng Object ).

Ví dụ thực tế: trong Spring Framework, bạn không thể triển khai ApplicationListener<? extends ApplicationEvent>nhiều lần. Nếu bạn cần các hành vi khác nhau, Tbạn cần kiểm trainstanceof

Bạn không thể làm mới T ()

(và bạn cần tham chiếu đến Lớp để làm điều đó)

Như những người khác đã nhận xét, việc làm tương đương với new T()chỉ có thể được thực hiện thông qua phản chiếu, chỉ bằng cách gọi một thể hiện của Class<T>, đảm bảo về các tham số được yêu cầu bởi hàm tạo. C # cho phép bạn làm new T() chỉ nếu bạn hạn chế Tđể xây dựng parameterless. Nếu Tkhông tôn trọng ràng buộc đó, lỗi biên dịch sẽ xuất hiện.

Trong Java, bạn thường sẽ bị buộc phải viết các phương thức giống như sau

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Những hạn chế trong đoạn mã trên là:

  • Class.newInstance chỉ hoạt động với một hàm tạo không tham số. Nếu không có sẵn, ReflectiveOperationExceptionsẽ được ném vào thời gian chạy
  • Hàm tạo phản ánh không làm nổi bật các vấn đề tại thời điểm biên dịch như ở trên. Nếu bạn cấu trúc lại, trong số bạn hoán đổi các đối số, bạn sẽ chỉ biết trong thời gian chạy

Nếu tôi là tác giả của C #, tôi sẽ giới thiệu khả năng chỉ định một hoặc nhiều ràng buộc phương thức khởi tạo dễ xác minh tại thời điểm biên dịch (vì vậy, tôi có thể yêu cầu một phương thức khởi tạo có tham số chẳng hạn string,string). Nhưng điều cuối cùng là suy đoán


2

Một điểm bổ sung mà không có câu trả lời nào khác có vẻ đã xem xét: nếu bạn thực sự cần số liệu chung với tính năng nhập thời gian chạy, bạn có thể tự triển khai nó như sau:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Sau đó, lớp này có thể thực hiện tất cả những điều có thể đạt được theo mặc định nếu Java không sử dụng tính năng tẩy: nó có thể cấp phát các Ts mới (giả sử Tcó một hàm tạo phù hợp với mẫu mà nó mong đợi sử dụng) hoặc các mảng của Ts, nó có thể kiểm tra động tại thời điểm chạy nếu một đối tượng cụ thể là a Tvà thay đổi hành vi tùy thuộc vào đối tượng đó, v.v.

Ví dụ:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Bất cứ ai quan tâm để giải thích lý do cho phiếu giảm giá? Theo như tôi thấy, đây là một lời giải thích hoàn toàn tốt về lý do tại sao nhược điểm được cho là của hệ thống xóa kiểu của Java thực sự không phải là một hạn chế thực sự. Vậy vấn đề là gì?
Jules

Tôi ủng hộ. Không phải tôi hỗ trợ kiểm tra kiểu thời gian chạy, nhưng thủ thuật này có thể hữu ích trong các mỏ muối.
techtangents

3
Java một ngôn ngữ của các mỏ muối, điều này làm cho điều này trở nên vô cùng cần thiết do thiếu thông tin về kiểu thời gian chạy. Hy vọng rằng tính năng xóa kiểu sẽ được hoàn tác trong phiên bản Java trong tương lai, làm cho tính năng chung trở thành một tính năng hữu ích hơn nhiều. Hiện tại, sự đồng thuận là tỷ lệ lực đẩy / trọng lượng của chúng là dưới 1
Marko Topolnik

Chà, chắc chắn rồi ... Miễn là TargetType của bạn không sử dụng các chỉ số chung. Nhưng điều đó có vẻ khá hạn chế.
Cơ bản

Nó cũng hoạt động với các loại chung chung. Hạn chế duy nhất là nếu bạn muốn tạo một thể hiện, bạn cần một phương thức khởi tạo với các loại đối số phù hợp, nhưng mọi ngôn ngữ khác mà tôi biết đều có cùng một ràng buộc, vì vậy tôi không thấy hạn chế.
Jules

0

tránh c ++ - giống như mã phình to vì cùng một mã được sử dụng cho nhiều loại; tuy nhiên, việc xóa kiểu yêu cầu gửi ảo trong khi phương pháp c ++ - code-bloat có thể thực hiện các generic không được gửi ảo


3
Tuy nhiên, hầu hết các công văn ảo đó đều được chuyển đổi thành tĩnh trong thời gian chạy, vì vậy nó không quá tệ như nó có vẻ.
Piotr Kołaczkowski

1
rất đúng, sự sẵn có của một trình biên dịch tại thời điểm chạy cho phép java thực hiện rất nhiều điều thú vị (và đạt được hiệu suất cao) so với c ++.
Necromancer

wow, hãy để tôi viết nó xuống đâu đó java đạt được hiệu suất cao so với cpp ..
jungle_mole

1
@jungle_mole vâng, cảm ơn bạn. ít người nhận ra lợi ích của việc có sẵn một trình biên dịch tại thời điểm chạy để biên dịch lại mã sau khi biên dịch. (hoặc việc thu gom rác là big-oh (không phải rác) chứ không phải là niềm tin ngây thơ và không chính xác rằng thu gom rác là big-oh (rác), (hoặc rằng trong khi thu gom rác là một nỗ lực nhỏ, nó bù đắp bằng cách cho phép chi phí bằng không phân bổ)). Đó là một thế giới đáng buồn, nơi mọi người chấp nhận một cách mù quáng những gì cộng đồng của họ tin tưởng và ngừng lý luận từ những nguyên tắc đầu tiên.
Necromancer

0

Hầu hết các câu trả lời đều quan tâm đến triết lý lập trình hơn là các chi tiết kỹ thuật thực tế.

Và mặc dù câu hỏi này đã được hơn 5 năm, câu hỏi vẫn còn tồn tại: Tại sao loại xóa được mong muốn từ quan điểm kỹ thuật? Cuối cùng, câu trả lời khá đơn giản (ở cấp độ cao hơn): https://en.wikipedia.org/wiki/Type_erasure

Các mẫu C ++ không tồn tại trong thời gian chạy. Trình biên dịch tạo ra một phiên bản được tối ưu hóa hoàn toàn cho mỗi lệnh gọi, có nghĩa là việc thực thi không phụ thuộc vào thông tin kiểu. Nhưng làm thế nào để JIT đối phó với các phiên bản khác nhau của cùng một chức năng? Sẽ tốt hơn nếu chỉ có một chức năng? Không muốn JIT phải tối ưu hóa tất cả các phiên bản khác nhau của nó. Chà, nhưng sau đó về loại an toàn thì sao? Đoán rằng phải đi ra ngoài cửa sổ.

Nhưng hãy đợi một chút: .NET làm điều đó như thế nào? Suy ngẫm! Bằng cách này, họ chỉ phải tối ưu hóa một chức năng và cũng nhận được thông tin loại thời gian chạy. Và đó là lý do tại sao .NET generics thường chậm hơn (mặc dù chúng đã tốt hơn nhiều). Tôi không lập luận rằng điều đó không thuận tiện! Nhưng nó đắt tiền và không nên được sử dụng khi không thực sự cần thiết (nó không được coi là đắt tiền trong các ngôn ngữ được nhập động vì trình biên dịch / thông dịch viên vẫn dựa vào sự phản chiếu).

Theo cách này, lập trình chung với tính năng xóa kiểu gần bằng không (vẫn yêu cầu một số kiểm tra / ép thời gian chạy): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

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.