Sự khác biệt giữa C # và toán tử bậc ba của Java (? :)


94

Tôi là một người mới sử dụng C # và tôi vừa gặp phải một vấn đề. Có sự khác biệt giữa C # và Java khi xử lý toán tử bậc ba ( ? :).

Trong đoạn mã sau, tại sao dòng thứ 4 không hoạt động? Trình biên dịch hiển thị thông báo lỗi của there is no implicit conversion between 'int' and 'string'. Dòng thứ 5 cũng không hoạt động. Cả hai đều Listlà đồ vật, phải không?

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

Tuy nhiên, mã tương tự hoạt động trong Java:

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

Tính năng ngôn ngữ nào trong C # bị thiếu? Nếu có, tại sao nó không được thêm vào?


4
Theo msdn.microsoft.com/en-us/library/ty67wk28.aspx "Loại biểu thức đầu tiên và biểu thức thứ hai phải giống nhau hoặc một chuyển đổi ngầm định phải tồn tại từ loại này sang loại khác."
Egor

2
Lâu rồi mình chưa làm được C #, nhưng thông báo mà bạn trích dẫn không nói lên điều đó sao? Hai nhánh của bậc ba phải trả về cùng một kiểu dữ liệu. Bạn đang cho nó một int và một chuỗi. C # không biết cách tự động chuyển đổi một int thành một chuỗi. Bạn cần đặt một chuyển đổi loại rõ ràng trên đó.
Jay

2
@ blackr1234 Vì an intkhông phải là một đối tượng. Nó là một nguyên thủy.
Code-Apprentice vào

3
@ Code-Apprentice vâng, chúng thực sự rất khác nhau - C # đã sửa đổi thời gian chạy, vì vậy danh sách thuộc các loại không liên quan. Oh, và bạn cũng có thể ném generic giao diện hiệp phương sai / contravariance vào trộn nếu bạn muốn, để giới thiệu một số mức độ mối quan hệ;)
Lucas Trzesniewski

3
Vì lợi ích của OP: loại xóa trong Java có nghĩa là như vậy ArrayList<String>ArrayList<Integer>trở thành chỉ ArrayListtrong bytecode. Điều này có nghĩa là chúng dường như là cùng một loại tại thời điểm chạy. Rõ ràng trong C # chúng là các loại khác nhau.
Code-Apprentice vào

Câu trả lời:


106

Nhìn qua Đặc tả ngôn ngữ C # 5 phần 7.14: Toán tử điều kiện, chúng ta có thể thấy như sau:

  • Nếu x có loại X và y có loại Y thì

    • Nếu tồn tại một phép chuyển đổi ngầm định (§6.1) từ X sang Y, nhưng không tồn tại từ Y sang X, thì Y là kiểu của biểu thức điều kiện.

    • Nếu tồn tại một phép chuyển đổi ngầm định (§6.1) từ Y sang X, nhưng không tồn tại từ X sang Y, thì X là kiểu của biểu thức điều kiện.

    • Nếu không, không có loại biểu thức nào có thể được xác định và xảy ra lỗi thời gian biên dịch

Nói cách khác: nó cố gắng tìm xem có thể chuyển đổi x và y thành khác nhau hay không và nếu không, lỗi biên dịch sẽ xảy ra. Trong trường hợp của chúng tôi intstringkhông có chuyển đổi rõ ràng hoặc ẩn nên nó sẽ không biên dịch.

Đối chiếu điều này với phần Đặc tả ngôn ngữ Java 7 15.25: Toán tử có điều kiện :

  • Nếu toán hạng thứ hai và thứ ba có cùng kiểu (có thể là kiểu rỗng), thì đó là kiểu của biểu thức điều kiện. ( KHÔNG )
  • Nếu một trong các toán hạng thứ hai và thứ ba có kiểu nguyên thủy T và kiểu của toán hạng kia là kết quả của việc áp dụng phép chuyển đổi quyền anh (§5.1.7) thành T, thì kiểu của biểu thức điều kiện là T. ( NO )
  • Nếu một trong các toán hạng thứ hai và thứ ba thuộc kiểu null và kiểu của toán hạng kia là kiểu tham chiếu, thì kiểu của biểu thức điều kiện là kiểu tham chiếu đó. ( KHÔNG )
  • Ngược lại, nếu toán hạng thứ hai và thứ ba có kiểu có thể chuyển đổi (§5.1.8) thành kiểu số, thì có một số trường hợp: ( KHÔNG )
  • Nếu không, toán hạng thứ hai và thứ ba có kiểu S1 và S2 tương ứng. Gọi T1 là kiểu kết quả từ việc áp dụng chuyển đổi quyền anh cho S1 và đặt T2 là kiểu kết quả từ việc áp dụng chuyển đổi quyền anh cho S2.
    Loại biểu thức điều kiện là kết quả của việc áp dụng chuyển đổi bắt (§5.1.10) thành lub (T1, T2) (§15.12.2.7). ( )

Và, xem phần 15.12.2.7. Các đối số kiểu suy ra Dựa trên các đối số thực tế, chúng ta có thể thấy nó cố gắng tìm một tổ tiên chung sẽ đóng vai trò là kiểu được sử dụng cho lệnh gọi mà nó đến Object. Object một đối số được chấp nhận để cuộc gọi sẽ hoạt động.


1
Tôi đọc thông số kỹ thuật C # nói rằng phải có một chuyển đổi ngầm định theo ít nhất một hướng. Lỗi trình biên dịch chỉ xảy ra khi không có chuyển đổi ngầm định theo một trong hai hướng.
Code-Apprentice vào

1
Tất nhiên, đây vẫn là câu trả lời đầy đủ nhất vì bạn trích dẫn trực tiếp từ thông số kỹ thuật.
Code-Apprentice vào

Điều này thực sự chỉ được thay đổi với Java 5 (tôi nghĩ, có thể là 6). Trước đó Java có hành vi giống hệt như C #. Do cách thực thi enums, điều này có thể gây ra nhiều điều bất ngờ hơn trong Java so với C #, mặc dù vậy, tất cả đều có một sự thay đổi tốt.
Voo

@Voo: FYI, Java 5 và Java 6 có cùng một phiên bản thông số kỹ thuật (Đặc điểm kỹ thuật ngôn ngữ Java , Phiên bản thứ ba), vì vậy chắc chắn nó không thay đổi trong Java 6.
ruakh

86

Các câu trả lời được đưa ra là tốt; Tôi muốn nói thêm với họ rằng quy tắc này của C # là hệ quả của một hướng dẫn thiết kế chung hơn. Khi được yêu cầu suy ra loại biểu thức từ một trong số các lựa chọn, C # chọn giá trị tốt nhất duy nhất trong số đó . Có nghĩa là, nếu bạn cung cấp cho C # một số lựa chọn như "Hươu cao cổ, Động vật có vú, Động vật" thì nó có thể chọn chung nhất - Động vật - hoặc nó có thể chọn cụ thể nhất - Hươu cao cổ - tùy thuộc vào hoàn cảnh. Nhưng nó phải chọn một trong những lựa chọn mà nó đã thực sự đưa ra . C # không bao giờ nói "lựa chọn của tôi là giữa Mèo và Chó, do đó tôi sẽ suy ra rằng Động vật là lựa chọn tốt nhất". Đó không phải là một lựa chọn được đưa ra, vì vậy C # không thể chọn nó.

Trong trường hợp toán tử bậc ba, C # cố gắng chọn kiểu int và chuỗi tổng quát hơn, nhưng không phải kiểu tổng quát hơn. Thay vì chọn một kiểu không phải là lựa chọn ngay từ đầu, như đối tượng, C # quyết định rằng không có kiểu nào có thể suy ra được.

Tôi cũng lưu ý rằng điều này phù hợp với một nguyên tắc thiết kế khác của C #: nếu có gì đó không ổn, hãy nói với nhà phát triển. Ngôn ngữ không có nội dung "Tôi sẽ đoán ý của bạn và tìm hiểu kỹ nếu tôi có thể". Ngôn ngữ nói rằng "Tôi nghĩ rằng bạn đã viết một cái gì đó khó hiểu ở đây, và tôi sẽ nói với bạn về điều đó."

Ngoài ra, tôi lưu ý rằng C # không suy luận từ biến thành giá trị được gán , mà là theo hướng khác. C # không nói "bạn đang gán cho một biến đối tượng do đó biểu thức phải có thể chuyển đổi thành đối tượng, do đó tôi sẽ đảm bảo rằng nó là như vậy". Đúng hơn, C # nói "biểu thức này phải có một kiểu và tôi phải có thể suy ra rằng kiểu đó tương thích với đối tượng". Vì biểu thức không có kiểu nên sẽ xảy ra lỗi.


6
Qua đoạn đầu mình thắc mắc tại sao hiệp phương sai / trái ngược đột ngột quan tâm, đến đoạn 2 thì bắt đầu đồng ý hơn, rồi xem ai viết câu trả lời ... Nên đoán sớm hơn. Bạn đã bao giờ nhận thấy chính xác mức độ bạn sử dụng 'Giraffe' làm ví dụ chưa? Bạn phải thực sự thích Giraffes.
Pharap

21
Hươu cao cổ thật tuyệt vời!
Eric Lippert

9
@Pharap: Nói một cách nghiêm túc, có những lý do sư phạm khiến tôi sử dụng hươu cao cổ quá nhiều. Trước hết, hươu cao cổ nổi tiếng khắp thế giới. Thứ hai, đó là một loại từ thú vị để nói, và chúng trông rất kỳ quặc nên đó là một hình ảnh đáng nhớ. Thứ ba, việc sử dụng, ví dụ, "hươu cao cổ" và "rùa" trong cùng một phép loại suy nhấn mạnh "hai lớp này, mặc dù chúng có thể chia sẻ một lớp cơ sở, là những động vật rất khác nhau với những đặc điểm rất khác nhau". Các câu hỏi về hệ thống kiểu thường có các ví dụ trong đó các kiểu rất giống nhau về mặt lôgic, điều này khiến trực giác của bạn đi sai hướng.
Eric Lippert

7
@Pharap: Đó là, câu hỏi thường là "tại sao không thể sử dụng danh sách các nhà máy cung cấp hóa đơn của khách hàng làm danh sách các nhà máy cung cấp hóa đơn?" và trực giác của bạn nói rằng "vâng, những điều đó về cơ bản giống nhau", nhưng khi bạn nói "tại sao một căn phòng đầy hươu cao cổ lại không thể được sử dụng như một căn phòng đầy động vật có vú?" thì nó trở nên giống một cách ví von trực quan hơn khi nói "bởi vì một căn phòng đầy động vật có vú có thể chứa một con hổ, một căn phòng đầy hươu cao cổ thì không thể".
Eric Lippert

6
@Eric Ban đầu tôi gặp sự cố này int? b = (a != 0 ? a : (int?) null). Ngoài ra còn có câu hỏi này , cùng với tất cả các câu hỏi được liên kết ở bên cạnh. Nếu bạn tiếp tục theo các liên kết, có khá nhiều. Để so sánh, tôi chưa bao giờ nghe nói về ai đó gặp phải vấn đề trong thế giới thực với cách Java thực hiện.
BlueRaja - Danny Pflughoeft 07/02/16

24

Về phần generics:

two > six ? new List<int>() : new List<string>()

Trong C #, trình biên dịch cố gắng chuyển đổi các phần biểu thức bên phải thành một số kiểu thông thường; vì List<int>List<string>là hai kiểu được xây dựng riêng biệt, không thể chuyển đổi một kiểu này sang kiểu kia.

Trong Java, trình biên dịch cố gắng tìm một siêu kiểu chung thay vì chuyển đổi, vì vậy việc biên dịch mã liên quan đến việc sử dụng ngầm các ký tự đại diệntẩy xóa kiểu ;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

có kiểu biên dịch ArrayList<?>(thực ra, nó cũng có thể là ArrayList<? extends Serializable>hoặc ArrayList<? extends Comparable<?>>, tùy thuộc vào ngữ cảnh sử dụng, vì cả hai đều là siêu kiểu chung chung phổ biến) và kiểu thời gian chạy là thô ArrayList(vì đó là siêu kiểu thô phổ biến).

Ví dụ (tự kiểm tra) ,

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED

Trên thực tế Write(two > six ? new List<object>() : new List<string>());cũng không hoạt động.
blackr1234

2
@ blackr1234 chính xác: Tôi không muốn lặp lại những gì các câu trả lời khác đã nói, nhưng biểu thức bậc ba cần đánh giá thành một kiểu và trình biên dịch sẽ không cố gắng tìm "mẫu số chung thấp nhất" trong hai kiểu.
Mathieu Guindon

2
Trên thực tế, đây không phải là về việc xóa kiểu mà là về các kiểu ký tự đại diện. Các phép xóa của ArrayList<String>ArrayList<Integer>sẽ là ArrayList(kiểu thô), nhưng kiểu suy ra cho toán tử bậc ba này là ArrayList<?>(kiểu ký tự đại diện). Nói một cách tổng quát hơn, thời gian chạy Java thực hiện các định dạng chung thông qua tính năng xóa kiểu không ảnh hưởng đến kiểu thời gian biên dịch của một biểu thức.
khen ngợi vào

1
@vaxquis cảm ơn! Tôi là một chàng trai C #, Java Generics nhầm lẫn tôi ;-)
Mathieu Guindon

2
Trên thực tế, các loại hình two > six ? new ArrayList<Integer>() : new ArrayList<String>()ArrayList<? extends Serializable&Comparable<?>>có nghĩa là bạn có thể gán nó vào một biến kiểu ArrayList<? extends Serializable>cũng như một biến kiểu ArrayList<? extends Comparable<?>>, nhưng tất nhiên ArrayList<?>, đó là tương đương với ArrayList<? extends Object>, công trình là tốt.
Holger

6

Trong cả Java và C # (và hầu hết các ngôn ngữ khác), kết quả của một biểu thức có một kiểu. Trong trường hợp toán tử bậc ba, có hai biểu thức con có thể được đánh giá cho kết quả và cả hai phải có cùng kiểu. Trong trường hợp của Java, một intbiến có thể được chuyển đổi thành một Integerbằng cách tự động đóng hộp. Bây giờ vì cả hai IntegerStringkế thừa từ Object, chúng có thể được chuyển đổi thành cùng một loại bằng một chuyển đổi thu hẹp đơn giản.

Mặt khác, trong C #, an intlà một nguyên thủy và không có chuyển đổi ngầm định sang stringhay bất kỳ thứ gì khác object.


Cảm ơn vì câu trả lời. Autoboxing là những gì tôi hiện đang nghĩ đến. C # không cho phép autoboxing?
blackr1234

2
Tôi không nghĩ rằng điều này là chính xác vì việc chuyển một int vào một đối tượng là hoàn toàn có thể xảy ra trong C #. Sự khác biệt ở đây là, tôi tin rằng, C # chỉ thực hiện một cách tiếp cận rất thoải mái trong việc xác định xem nó có được phép hay không.
Jeroen Vannevel

9
Điều này không liên quan gì đến quyền anh tự động, điều này sẽ xảy ra nhiều như trong C #. Sự khác biệt là C # không cố gắng tìm một siêu kiểu chung trong khi Java (chỉ vì Java 5!). Bạn có thể dễ dàng kiểm tra điều đó bằng cách thử xem điều gì sẽ xảy ra nếu bạn thử nó với 2 lớp tùy chỉnh (cả hai đều kế thừa từ đối tượng rõ ràng). Ngoài ra, có một chuyển đổi ngầm định từ intthành object.
Voo

3
Và đối với nitpick chỉ một chút nữa: Chuyển đổi từ Integer/Stringsang Objectkhông phải là chuyển đổi thu hẹp, nó hoàn toàn ngược lại :-)
Voo

1
IntegerStringcũng thực hiện SerializableComparabledo đó các nhiệm vụ cho một trong hai cũng sẽ hoạt động, ví dụ Comparable<?> c=condition? 6: "6";hoặc List<? extends Serializable> l = condition? new ArrayList<Integer>(): new ArrayList<String>();là mã Java hợp pháp.
Holger

5

Việc này thật thẳng thắn. Không có chuyển đổi ngầm giữa chuỗi và int. toán tử bậc ba cần hai toán hạng cuối cùng có cùng kiểu.

Thử:

Write(two > six ? two.ToString() : "6");

11
Câu hỏi là nguyên nhân gây ra sự khác biệt giữa Java và C #. Điều này không trả lời câu hỏi.
Jeroen Vannevel
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.