Sự khác biệt giữa Generics trong C # và Java và các mẫu trong C ++ là gì? [đóng cửa]


203

Tôi chủ yếu sử dụng Java và generic là tương đối mới. Tôi tiếp tục đọc rằng Java đã đưa ra quyết định sai hoặc .NET có triển khai tốt hơn, v.v.

Vậy, sự khác biệt chính giữa C ++, C #, Java trong khái quát là gì? Ưu / nhược điểm của từng loại?

Câu trả lời:


364

Tôi sẽ thêm tiếng nói của mình vào tiếng ồn và cố gắng làm cho mọi thứ rõ ràng:

C # Generics cho phép bạn khai báo một cái gì đó như thế này.

List<Person> foo = new List<Person>();

và sau đó trình biên dịch sẽ ngăn bạn đưa những thứ không có Persontrong danh sách.
Đằng sau hậu trường, trình biên dịch C # chỉ đưa List<Person>vào tệp .NET dll, nhưng trong thời gian chạy, trình biên dịch JIT sẽ xây dựng một bộ mã mới, như thể bạn đã viết một lớp danh sách đặc biệt chỉ để chứa người - đại loại như thế ListOfPerson.

Lợi ích của việc này là nó làm cho nó thực sự nhanh chóng. Không có bất kỳ nội dung nào hoặc bất kỳ nội dung nào khác, và vì dll chứa thông tin rằng đây là Danh sách Person, mã khác nhìn vào sau này bằng cách sử dụng sự phản chiếu có thể cho biết rằng nó có chứa Personcác đối tượng (vì vậy bạn nhận được thông báo và vân vân).

Nhược điểm của điều này là mã C # 1.0 và 1.1 cũ (trước khi chúng thêm tướng) không hiểu những cái mới này List<something>, vì vậy bạn phải tự chuyển đổi mọi thứ trở lại cũ Listđể tương tác với chúng. Đây không phải là vấn đề lớn, bởi vì mã nhị phân C # 2.0 không tương thích ngược. Lần duy nhất điều này sẽ xảy ra là nếu bạn nâng cấp một số mã C # 1.0 / 1.1 cũ lên C # 2.0

Java Generics cho phép bạn khai báo một cái gì đó như thế này.

ArrayList<Person> foo = new ArrayList<Person>();

Nhìn bề ngoài, nó trông giống nhau, và nó giống như vậy. Trình biên dịch cũng sẽ ngăn bạn đưa những thứ không có Persontrong danh sách.

Sự khác biệt là những gì xảy ra đằng sau hậu trường. Không giống như C #, Java không đi và xây dựng một thứ đặc biệt ListOfPerson- nó chỉ sử dụng cái cũ đơn giản ArrayListluôn có trong Java. Khi bạn lấy mọi thứ ra khỏi mảng, Person p = (Person)foo.get(1);điệu nhảy đúc thông thường vẫn phải được thực hiện. Trình biên dịch đang tiết kiệm cho bạn các lần bấm phím, nhưng tốc độ nhấn / truyền vẫn còn phát sinh như mọi khi.
Khi mọi người đề cập đến "Loại xóa", đây là những gì họ đang nói về. Trình biên dịch chèn các phôi cho bạn, và sau đó 'xóa' thực tế rằng nó có nghĩa là một danh sách Personkhông chỉObject

Lợi ích của phương pháp này là mã cũ không hiểu khái quát không cần phải quan tâm. Nó vẫn xử lý như cũ ArrayListnhư mọi khi. Điều này quan trọng hơn trong thế giới java vì họ muốn hỗ trợ biên dịch mã bằng Java 5 với các tổng quát và để nó chạy trên 1.4 hoặc các JVM cũ, mà microsoft đã cố tình quyết định không bận tâm.

Nhược điểm là tốc độ mà tôi đã đề cập trước đây và cũng vì không có ListOfPersonlớp giả hoặc bất cứ thứ gì giống như đi vào các tệp. Class, mã nhìn vào nó sau này (với sự phản chiếu hoặc nếu bạn rút nó ra khỏi bộ sưu tập khác trong đó nó được chuyển đổi thành Objecthay không) không thể nói theo bất kỳ cách nào nó có nghĩa là một danh sách chỉ chứa Personvà không chỉ bất kỳ danh sách mảng nào khác.

Mẫu C ++ cho phép bạn khai báo một cái gì đó như thế này

std::list<Person>* foo = new std::list<Person>();

Nó trông giống như chung chung của C # và Java, và nó sẽ làm những gì bạn nghĩ nó nên làm, nhưng đằng sau hậu trường, những điều khác nhau đang xảy ra.

Nó có điểm chung nhất với các tướng C # ở chỗ nó xây dựng đặc biệt pseudo-classesthay vì chỉ ném thông tin loại như java, nhưng đó là một ấm cá hoàn toàn khác.

Cả C # và Java đều tạo ra đầu ra được thiết kế cho các máy ảo. Nếu bạn viết một số mã có một Personlớp trong đó, trong cả hai trường hợp, một số thông tin về một Personlớp sẽ đi vào tệp dll hoặc. Class và JVM / CLR sẽ thực hiện công việc này.

C ++ tạo mã nhị phân x86 thô. Mọi thứ không phải là một đối tượng và không có máy ảo cơ bản nào cần biết về một Personlớp. Không có quyền anh hoặc unboxing, và các chức năng không phải thuộc về các lớp học, hoặc thực sự bất cứ điều gì.

Do đó, trình biên dịch C ++ không hạn chế những gì bạn có thể làm với các mẫu - về cơ bản bất kỳ mã nào bạn có thể viết thủ công, bạn có thể lấy các mẫu để viết cho bạn.
Ví dụ rõ ràng nhất là thêm những thứ:

Trong C # và Java, hệ thống generic cần biết phương thức nào có sẵn cho một lớp và nó cần truyền lại cho máy ảo. Cách duy nhất để nói với nó điều này là bằng cách mã hóa cứng lớp thực tế hoặc sử dụng các giao diện. Ví dụ:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Mã đó sẽ không được biên dịch trong C # hoặc Java, vì nó không biết rằng loại Tthực sự cung cấp một phương thức gọi là Tên (). Bạn phải nói với nó - trong C # như thế này:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

Và sau đó, bạn phải đảm bảo những điều bạn vượt qua để addNames thực hiện giao diện IHasName, v.v. Cú pháp java là khác nhau ( <T extends IHasName>), nhưng nó chịu cùng một vấn đề.

Trường hợp 'cổ điển' cho vấn đề này là cố gắng viết một hàm thực hiện điều này

string addNames<T>( T first, T second ) { return first + second; }

Bạn thực sự không thể viết mã này vì không có cách nào để khai báo một giao diện với +phương thức trong đó. Bạn thất bại.

C ++ không gặp phải vấn đề nào trong số này. Trình biên dịch không quan tâm đến việc chuyển các loại xuống bất kỳ máy ảo nào - nếu cả hai đối tượng của bạn đều có hàm .Name (), nó sẽ biên dịch. Nếu họ không, nó sẽ không. Đơn giản.

Vì vậy, bạn có nó :-)


8
Giả danh được tạo cho các loại tham chiếu trong C # chia sẻ cùng cách triển khai để bạn không có chính xác ListOfP People. Kiểm tra blog.msdn.com/ericlippert/archive/2009/07/30/ từ
Piotr Czapla

4
Không, bạn không thể biên dịch mã Java 5 bằng cách sử dụng chung và chạy nó trên 1.4 VM cũ (ít nhất là Sun JDK không thực hiện điều này. Một số công cụ của bên thứ 3 làm.) Những gì bạn có thể làm là sử dụng 1.4 JAR được biên dịch trước đó từ Mã 1.5 / 1.6.
Finnw

4
Tôi phản đối tuyên bố mà bạn không thể viết int addNames<T>( T first, T second ) { return first + second; }bằng C #. Kiểu chung có thể được giới hạn trong một lớp thay vì giao diện và có một cách để khai báo một lớp với +toán tử trong đó.
Mashmagar

4
@AlexanderMalakhov đó là mục đích không thành ngữ. Vấn đề không phải là để giáo dục về các thành ngữ của C ++, mà là để minh họa cách đoạn mã giống nhau được xử lý khác nhau bởi mỗi ngôn ngữ. Mục tiêu này sẽ khó hơn để đạt được mã trông khác biệt hơn
Orion Edwards

3
@phresnel Tôi đồng ý về nguyên tắc, nhưng nếu tôi đã viết đoạn trích đó trong C ++ thành ngữ, thì các nhà phát triển C # / Java sẽ khó hiểu hơn nhiều và do đó (tôi tin) sẽ làm một công việc tồi tệ hơn trong việc giải thích sự khác biệt. Chúng ta hãy đồng ý không đồng ý về điều này :-)
Orion Edwards

61

C ++ hiếm khi sử dụng thuật ngữ khái quát của YouTube. Thay vào đó, các mẫu từ Wikipedia được sử dụng và chính xác hơn. Mẫu mô tả một kỹ thuật để đạt được một thiết kế chung.

Các mẫu C ++ rất khác so với những gì cả C # và Java thực hiện vì hai lý do chính. Lý do đầu tiên là các mẫu C ++ không chỉ cho phép các đối số kiểu thời gian biên dịch mà còn cho các đối số const-value thời gian biên dịch: các mẫu có thể được cung cấp dưới dạng số nguyên hoặc thậm chí cả chữ ký hàm. Điều này có nghĩa là bạn có thể thực hiện một số công cụ khá thú vị vào thời gian biên dịch, ví dụ: tính toán:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Mã này cũng sử dụng tính năng phân biệt khác của các mẫu C ++, cụ thể là chuyên môn hóa mẫu. Mã định nghĩa một mẫu lớp, productcó một đối số giá trị. Nó cũng định nghĩa một chuyên môn hóa cho mẫu đó được sử dụng bất cứ khi nào đối số ước tính thành 1. Điều này cho phép tôi xác định đệ quy trên các định nghĩa mẫu. Tôi tin rằng điều này lần đầu tiên được phát hiện bởi Andrei Alexandrescu .

Chuyên môn hóa mẫu rất quan trọng đối với C ++ vì nó cho phép có sự khác biệt về cấu trúc trong cấu trúc dữ liệu. Các mẫu nói chung là một phương tiện để thống nhất một giao diện giữa các loại. Tuy nhiên, mặc dù điều này là mong muốn, tất cả các loại không thể được đối xử bình đẳng trong quá trình thực hiện. Các mẫu C ++ đưa điều này vào tài khoản. Điều này rất giống với sự khác biệt mà OOP tạo ra giữa giao diện và triển khai với việc ghi đè các phương thức ảo.

Các mẫu C ++ rất cần thiết cho mô hình lập trình thuật toán của nó. Ví dụ, hầu hết tất cả các thuật toán cho vùng chứa được định nghĩa là các hàm chấp nhận loại vùng chứa là kiểu mẫu và xử lý chúng đồng nhất. Trên thực tế, điều đó không hoàn toàn đúng: C ++ không hoạt động trên các container mà thay vào đó là các phạm vi được xác định bởi hai trình vòng lặp, chỉ vào điểm bắt đầu và phía sau điểm cuối của vùng chứa. Do đó, toàn bộ nội dung được các vòng lặp chặn: bắt đầu <= phần tử <end.

Sử dụng các vòng lặp thay vì các thùng chứa là hữu ích vì nó cho phép hoạt động trên các bộ phận của một container thay vì trên toàn bộ.

Một đặc điểm khác biệt của C ++ là khả năng chuyên môn hóa một phần cho các mẫu lớp. Điều này có phần liên quan đến khớp mẫu trên các đối số trong Haskell và các ngôn ngữ chức năng khác. Ví dụ: hãy xem xét một lớp lưu trữ các phần tử:

template <typename T>
class Store {  }; // (1)

Điều này làm việc cho bất kỳ loại yếu tố. Nhưng hãy nói rằng chúng ta có thể lưu trữ con trỏ một cách hiệu quả hơn các loại khác bằng cách áp dụng một số thủ thuật đặc biệt. Chúng tôi có thể làm điều này bằng cách chuyên một phần cho tất cả các loại con trỏ:

template <typename T>
class Store<T*> {  }; // (2)

Bây giờ, bất cứ khi nào chúng ta ví dụ một mẫu container cho một loại, định nghĩa phù hợp sẽ được sử dụng:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

Đôi khi tôi đã ước tính năng generic trong .net có thể cho phép mọi thứ ngoài các loại được sử dụng làm khóa. Nếu các mảng kiểu giá trị là một phần của Khung (tôi ngạc nhiên là chúng không cần phải tương tác với các API cũ hơn nhúng các mảng có kích thước cố định trong các cấu trúc), sẽ rất hữu ích khi khai báo lớp chứa một vài mục riêng lẻ và sau đó là mảng kiểu giá trị có kích thước là tham số chung. Như vậy, cái gần nhất có thể đến là có một đối tượng lớp chứa các mục riêng lẻ và sau đó cũng giữ một tham chiếu đến một đối tượng riêng biệt giữ mảng.
supercat

@supercat Nếu bạn tương tác với API kế thừa, ý tưởng là sử dụng marshalling (có thể được chú thích thông qua các thuộc tính). CLR dù sao cũng không có mảng kích thước cố định nên việc có các đối số khuôn mẫu không phải là loại sẽ không có ích ở đây.
Konrad Rudolph

Tôi đoán điều tôi cảm thấy khó hiểu là có vẻ như việc có các mảng loại giá trị có kích thước cố định không nên khó và nó sẽ cho phép nhiều loại dữ liệu được sắp xếp theo tham chiếu thay vì theo giá trị. Mặc dù so sánh theo giá trị có thể hữu ích trong các trường hợp thực sự không thể xử lý theo bất kỳ cách nào khác, tôi sẽ coi soái ca là vượt trội trong hầu hết các trường hợp có thể sử dụng được, vì vậy cho phép các trường hợp đó bao gồm các cấu trúc được cố định mảng kích thước có vẻ như là một tính năng hữu ích.
supercat

BTW, một tình huống khác trong đó các tham số chung không loại sẽ hữu ích với các kiểu dữ liệu đại diện cho số lượng kích thước. Người ta có thể bao gồm thông tin thứ nguyên trong các trường hợp đại diện cho số lượng, nhưng có thông tin đó trong một loại sẽ cho phép người ta chỉ định rằng một bộ sưu tập có nghĩa vụ giữ các đối tượng đại diện cho một đơn vị kích thước cụ thể.
supercat

35

Bản thân Anders Hejlsberg đã mô tả sự khác biệt ở đây " Generics in C #, Java và C ++ ".


tôi thực sự thích cuộc phỏng vấn đó nó làm cho nó rõ ràng cho những người không phải c # như tôi những gì đang xảy ra với thuốc generic c #.
Johannes Schaub - litb

18

Đã có rất nhiều câu trả lời tốt về những gì sự khác biệt là, vì vậy hãy để tôi đưa ra một quan điểm hơi khác nhau và thêm lý do tại sao .

Như đã được giải thích, sự khác biệt chính là xóa kiểu , tức là trình biên dịch Java xóa các kiểu chung và chúng không kết thúc trong mã byte được tạo. Tuy nhiên, câu hỏi là: tại sao mọi người sẽ làm điều đó? Nó không có ý nghĩa! Hay không?

Chà, cái gì thay thế? Nếu bạn không thực hiện Generics trong ngôn ngữ, nơi làm bạn thực hiện chúng? Và câu trả lời là: trong Máy ảo. Mà phá vỡ khả năng tương thích ngược.

Kiểu xóa, mặt khác, cho phép bạn trộn các máy khách chung với các thư viện không chung chung. Nói cách khác: mã được biên dịch trên Java 5 vẫn có thể được triển khai thành Java 1.4.

Microsoft, tuy nhiên, đã quyết định phá vỡ tính tương thích ngược cho thuốc generic. Đó là lý do tại sao .NET Generics "tốt" hơn Java Generics.

Tất nhiên, Sun không phải là kẻ ngốc hay hèn nhát. Lý do tại sao họ "bực mình", là vì Java đã cũ hơn và phổ biến hơn so với .NET khi họ giới thiệu chung chung. (Chúng được giới thiệu gần như cùng một lúc ở cả hai thế giới.) Phá vỡ khả năng tương thích ngược sẽ là một nỗi đau rất lớn.

Nói cách khác: trong Java, Generics là một phần của Ngôn ngữ (có nghĩa là chúng chỉ áp dụng cho Java, không áp dụng cho các ngôn ngữ khác), trong .NET chúng là một phần của Máy ảo (có nghĩa là chúng áp dụng cho tất cả các ngôn ngữ, không phải chỉ C # và Visual Basic.NET).

So sánh điều này với các tính năng .NET như LINQ, biểu thức lambda, suy luận kiểu biến cục bộ, kiểu ẩn danh và cây biểu thức: đây là tất cả các tính năng ngôn ngữ . Đó là lý do tại sao có sự khác biệt tinh tế giữa VB.NET và C #: nếu các tính năng đó là một phần của VM, thì chúng sẽ giống nhau trong tất cả các ngôn ngữ. Nhưng CLR không thay đổi: nó vẫn giống với .NET 3.5 SP1 như trong .NET 2.0. Bạn có thể biên dịch chương trình C # sử dụng LINQ với trình biên dịch .NET 3.5 và vẫn chạy nó trên .NET 2.0, miễn là bạn không sử dụng bất kỳ thư viện .NET 3.5 nào. Điều đó sẽ không hoạt động với generic và .NET 1.1, nhưng nó sẽ hoạt động với Java và Java 1.4.


3
LINQ chủ yếu là một tính năng thư viện (mặc dù C # và VB cũng đã thêm đường cú pháp cùng với nó). Bất kỳ ngôn ngữ nào nhắm mục tiêu CLR 2.0 đều có thể sử dụng LINQ đầy đủ chỉ bằng cách tải cụm System.Core.
Richard Berg

Vâng, xin lỗi, tôi nên đã rõ ràng hơn wrt. LINQ. Tôi đã đề cập đến cú pháp truy vấn, không phải các toán tử truy vấn tiêu chuẩn đơn âm, các phương thức mở rộng LINQ hoặc giao diện IQueryable. Rõ ràng, bạn có thể sử dụng chúng từ bất kỳ ngôn ngữ .NET nào.
Jörg W Mittag

1
Tôi đang nghĩ một lựa chọn khác cho Java. Ngay cả Oracle không muốn phá vỡ tính tương thích ngược, họ vẫn có thể thực hiện một số thủ thuật biên dịch để tránh thông tin loại bị xóa. Ví dụ, ArrayList<T>có thể được phát ra dưới dạng một kiểu mới được đặt tên nội bộ với Class<T>trường tĩnh (ẩn) . Miễn là phiên bản mới của lib chung đã được triển khai với mã byte 1,5+, nó sẽ có thể chạy trên 1,4- JVM.
Động cơ Trái đất

14

Theo dõi bài viết trước của tôi.

Các mẫu là một trong những lý do chính khiến C ++ thất bại rất nhiều tại intellisense, bất kể IDE được sử dụng. Do chuyên môn hóa mẫu, IDE không bao giờ có thể thực sự chắc chắn nếu một thành viên nhất định tồn tại hay không. Xem xét:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Bây giờ, con trỏ đang ở vị trí được chỉ định và thật khó để IDE có thể nói tại thời điểm đó nếu và những gì, các thành viên acó. Đối với các ngôn ngữ khác, việc phân tích cú pháp sẽ đơn giản nhưng đối với C ++, cần có khá nhiều đánh giá trước đó.

Nó trở nên tồi tệ hơn. Điều gì nếu my_int_typeđược định nghĩa bên trong một mẫu lớp? Bây giờ loại của nó sẽ phụ thuộc vào đối số loại khác. Và ở đây, ngay cả trình biên dịch thất bại.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Sau một chút suy nghĩ, một lập trình viên sẽ kết luận rằng mã này giống như trên: Y<int>::my_typegiải quyết int, do đó bnên cùng loại với a, phải không?

Sai lầm. Tại thời điểm trình biên dịch cố gắng giải quyết câu lệnh này, nó thực sự Y<int>::my_typechưa biết ! Vì vậy, nó không biết rằng đây là một loại. Nó có thể là một cái gì đó khác, ví dụ như một hàm thành viên hoặc một trường. Điều này có thể làm phát sinh sự mơ hồ (mặc dù không phải trong trường hợp hiện tại), do đó trình biên dịch thất bại. Chúng ta phải nói rõ rằng chúng ta đề cập đến một tên loại:

X<typename Y<int>::my_type> b;

Bây giờ, mã biên dịch. Để xem sự mơ hồ phát sinh từ tình huống này như thế nào, hãy xem xét đoạn mã sau:

Y<int>::my_type(123);

Câu lệnh mã này hoàn toàn hợp lệ và yêu cầu C ++ thực hiện lệnh gọi hàm Y<int>::my_type. Tuy nhiên, nếu my_typekhông phải là một hàm mà là một kiểu, câu lệnh này vẫn có hiệu lực và thực hiện một kiểu đúc đặc biệt (kiểu kiểu hàm) thường là một lời gọi của hàm tạo. Trình biên dịch không thể cho biết ý nghĩa của chúng tôi vì vậy chúng tôi phải định hướng ở đây.


2
Tôi hoàn toàn đồng ý. Có một số hy vọng, mặc dù. Hệ thống tự động hoàn thành và trình biên dịch C ++ phải tương tác rất chặt chẽ. Tôi khá chắc chắn rằng Visual Studio sẽ không bao giờ có tính năng như vậy, nhưng mọi thứ có thể xảy ra trong Eclipse / CDT hoặc một số IDE khác dựa trên GCC. HY VỌNG! :)
Benoît

6

Cả Java và C # đều giới thiệu thuốc generic sau khi phát hành ngôn ngữ đầu tiên. Tuy nhiên, có những khác biệt trong cách các thư viện cốt lõi thay đổi khi thuốc generic được giới thiệu. C # 's Generics không chỉ biên dịch ma thuật và vì vậy nó đã không thể generify lớp thư viện hiện có mà không phá vỡ tính tương thích ngược.

Ví dụ, trong Java, Bộ sưu tập Khung hiện tại đã hoàn toàn chung chung . Java không có cả phiên bản chung và không chung của các lớp bộ sưu tập. Theo một số cách, điều này sạch sẽ hơn nhiều - nếu bạn cần sử dụng một bộ sưu tập trong C # thì thực sự có rất ít lý do để đi với phiên bản không chung chung, nhưng các lớp kế thừa đó vẫn được giữ nguyên, làm lộn xộn cảnh quan.

Một sự khác biệt đáng chú ý khác là các lớp Enum trong Java và C #. Enum của Java có định nghĩa trông hơi khó hiểu này:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(xem phần giải thích rất rõ ràng của Angelika Langer về chính xác lý do tại sao lại như vậy. Về cơ bản, điều này có nghĩa là Java có thể cấp quyền truy cập an toàn từ một chuỗi đến giá trị Enum của nó:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

So sánh phiên bản này với phiên bản của C #:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Vì Enum đã tồn tại trong C # trước khi thuốc generic được đưa vào ngôn ngữ, định nghĩa không thể thay đổi nếu không phá vỡ mã hiện có. Vì vậy, giống như các bộ sưu tập, nó vẫn nằm trong các thư viện cốt lõi ở trạng thái cũ.


Ngay cả các tướng của C # không chỉ là ma thuật trình biên dịch, trình biên dịch có thể làm thêm phép thuật để tạo ra thư viện hiện có. Không có lý do tại sao họ cần đổi tên ArrayListthành List<T>và đặt nó vào một không gian tên mới. Thực tế là, nếu có một lớp xuất hiện trong mã nguồn vì ArrayList<T>nó sẽ trở thành một tên lớp trình biên dịch khác được tạo trong mã IL, do đó không có xung đột tên nào có thể xảy ra.
Động cơ Trái đất

4

Trễ 11 tháng, nhưng tôi nghĩ câu hỏi này đã sẵn sàng cho một số nội dung Java Wildcard.

Đây là một tính năng cú pháp của Java. Giả sử bạn có một phương pháp:

public <T> void Foo(Collection<T> thing)

Và giả sử bạn không cần phải tham khảo kiểu T trong thân phương thức. Bạn đang khai báo tên T và sau đó chỉ sử dụng một lần, vậy tại sao bạn phải nghĩ ra tên cho nó? Thay vào đó, bạn có thể viết:

public void Foo(Collection<?> thing)

Dấu hỏi yêu cầu trình biên dịch giả vờ rằng bạn đã khai báo một tham số loại có tên bình thường chỉ cần xuất hiện một lần ở vị trí đó.

Bạn không thể làm gì với các ký tự đại diện mà bạn cũng không thể làm với tham số loại được đặt tên (đó là cách những điều này luôn được thực hiện trong C ++ và C #).


2
Thêm 11 tháng nữa ... Có những điều bạn có thể làm với các ký tự đại diện Java mà bạn không thể có với các tham số loại được đặt tên. Bạn có thể làm điều này trong Java: class Foo<T extends List<?>>và sử dụng Foo<StringList>nhưng trong C #, bạn phải thêm tham số loại bổ sung đó: class Foo<T, T2> where T : IList<T2>và sử dụng clunky Foo<StringList, String>.
R. Martinho Fernandes



1

Các mẫu C ++ thực sự mạnh hơn nhiều so với các đối tác C # và Java của chúng khi chúng được đánh giá tại thời điểm biên dịch và hỗ trợ chuyên môn hóa. Điều này cho phép Lập trình siêu mẫu và làm cho trình biên dịch C ++ tương đương với máy Turing (tức là trong quá trình biên dịch, bạn có thể tính toán bất cứ thứ gì có thể tính toán được với máy Turing).


1

Trong Java, generic là cấp độ trình biên dịch, vì vậy bạn nhận được:

a = new ArrayList<String>()
a.getClass() => ArrayList

Lưu ý rằng loại 'a' là danh sách mảng, không phải danh sách các chuỗi. Vì vậy, loại danh sách chuối sẽ bằng () danh sách khỉ.

Vì vậy, để nói chuyện.


1

Có vẻ như, trong số các đề xuất rất thú vị khác, có một đề xuất về tinh chỉnh tổng quát và phá vỡ tính tương thích ngược:

Hiện tại, generic được thực hiện bằng cách sử dụng erasure, có nghĩa là thông tin loại chung không có sẵn trong thời gian chạy, điều này làm cho một số loại mã khó viết. Generics đã được thực hiện theo cách này để hỗ trợ khả năng tương thích ngược với mã không chung chung cũ. Tổng quát hóa sẽ làm cho thông tin loại chung có sẵn trong thời gian chạy, sẽ phá vỡ mã không chung chung. Tuy nhiên, Neal Gafter đã đề xuất các loại chỉ có thể hiển thị lại nếu được chỉ định, để không phá vỡ tính tương thích ngược.

tại bài viết của Alex Miller về các đề xuất Java 7


0

Lưu ý: Tôi không có đủ điểm để nhận xét, vì vậy hãy thoải mái chuyển nhận xét này thành nhận xét để trả lời thích hợp.

Trái với niềm tin phổ biến, mà tôi không bao giờ hiểu nó đến từ đâu, .net đã triển khai các khái quát thực sự mà không phá vỡ tính tương thích ngược và họ đã dành nỗ lực rõ ràng cho điều đó. Bạn không phải thay đổi mã .net 1.0 không chung chung thành chung chung để được sử dụng trong .net 2.0. Cả danh sách chung và không chung chung vẫn có sẵn trong .Net framework 2.0 ngay cả cho đến 4.0, chính xác không có gì khác ngoài lý do tương thích ngược. Do đó, các mã cũ vẫn sử dụng ArrayList không chung chung vẫn sẽ hoạt động và sử dụng cùng một lớp ArrayList như trước đây. Khả năng tương thích mã ngược luôn được duy trì từ 1.0 đến giờ ... Vì vậy, ngay cả trong .net 4.0, bạn vẫn phải tùy chọn sử dụng bất kỳ lớp không chung chung nào từ 1.0 BCL nếu bạn chọn làm như vậy.

Vì vậy, tôi không nghĩ java phải phá vỡ tính tương thích ngược để hỗ trợ các tổng quát thực sự.


Đó không phải là loại tương thích ngược mà mọi người nói đến. Ý tưởng là khả năng tương thích ngược cho thời gian chạy : Mã được viết bằng generic trong .NET 2.0 không thể chạy trên các phiên bản cũ hơn của .NET framework / CLR. Tương tự, nếu Java giới thiệu các tổng quát "thực", mã Java mới hơn sẽ không thể chạy trên các JVM cũ hơn (vì nó yêu cầu phá vỡ các thay đổi đối với mã byte).
tzaman

Đó là .net, không phải thuốc generic. Luôn yêu cầu biên dịch lại để nhắm mục tiêu phiên bản CLR cụ thể. Có khả năng tương thích mã byte, có khả năng tương thích mã. Ngoài ra, tôi đã trả lời cụ thể về nhu cầu chuyển đổi mã cũ đang sử dụng Danh sách cũ để sử dụng Danh sách tổng quát mới, điều này hoàn toàn không đúng.
Sheepy

1
Tôi nghĩ mọi người đang nói về sự tương đồng về phía trước . Tức là mã .net 2.0 để chạy trên .net 1.1, mã này sẽ bị hỏng vì thời gian chạy 1.1 không biết gì về 2.0 "lớp giả". Không phải là sau đó "java không thực hiện chung chung vì họ muốn duy trì khả năng tương thích về phía trước"? (thay vì lạc hậu)
Sheepy

Vấn đề tương thích là tinh tế. Tôi không nghĩ rằng vấn đề là việc thêm các tổng quát "thực" vào Java sẽ ảnh hưởng đến bất kỳ chương trình nào sử dụng các phiên bản Java cũ hơn, nhưng mã đó sử dụng các tổng quát "cải tiến mới" sẽ gặp khó khăn khi trao đổi các đối tượng như vậy với mã cũ hơn không biết gì về các loại mới. Giả sử, ví dụ, một chương trình có một chương trình ArrayList<Foo>mà nó muốn chuyển sang một phương thức cũ hơn được cho là để đưa vào một ArrayListthể hiện của Foo. Nếu ArrayList<foo>không phải là một ArrayList, làm thế nào để làm cho nó hoạt động?
supercat
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.