Điểm của toán tử kim cương (<>) trong Java 7 là gì?


445

Toán tử kim cương trong java 7 cho phép mã như sau:

List<String> list = new LinkedList<>();

Tuy nhiên, trong Java 5/6, tôi chỉ có thể viết:

List<String> list = new LinkedList();

Sự hiểu biết của tôi về loại tẩy là những cái này hoàn toàn giống nhau. (Cái chung được loại bỏ trong thời gian chạy anyway).

Tại sao phải bận tâm với kim cương cả? Những gì chức năng / loại an toàn mới mà nó cho phép? Nếu nó không mang lại bất kỳ chức năng mới nào thì tại sao họ lại đề cập đến nó như một tính năng? Là sự hiểu biết của tôi về khái niệm này là thiếu sót?


4
Lưu ý rằng đây không phải là vấn đề nếu bạn sử dụng các phương thức tĩnh của nhà máy, vì Java thực hiện suy luận về các cuộc gọi phương thức.
Brian Gordon

Khi bạn tắt cảnh báo, nó thực sự vô dụng ... :) như đối với tôi
Renetik

3
Nó đã được trả lời nhưng nó cũng nằm trong hướng dẫn Java (khoảng giữa trang): docs.oracle.com/javase/tutorial/java/generics/ Lỗi
Andreas Tasoulas

Bài viết hay về điều này trên dZone .
R. Oosterholt

2
Quan điểm của tôi về nó là đường cú pháp cho Danh sách <String> list = new LinkedList <Dù kiểu bên trái là> (); tức là giữ cho nó chung chung
Viktor Mellgren

Câu trả lời:


496

Vấn đề với

List<String> list = new LinkedList();

là ở phía bên tay trái, bạn đang sử dụng chung loại List<String>nơi ở phía bên phải bạn đang sử dụng các nguyên liệu loại LinkedList. Các kiểu thô trong Java thực sự chỉ tồn tại để tương thích với mã tiền chung và không bao giờ được sử dụng trong mã mới trừ khi bạn thực sự phải làm vậy.

Bây giờ, nếu Java có các tổng quát ngay từ đầu và không có các kiểu, chẳng hạn như LinkedListđược tạo ra trước khi có các tổng quát, thì có lẽ nó đã tạo ra nó để hàm tạo cho một kiểu chung tự động truyền các tham số kiểu của nó từ bên trái bên tay của nhiệm vụ nếu có thể. Nhưng nó đã không, và nó phải xử lý các loại thô và loại chung khác nhau để tương thích ngược. Điều đó khiến họ cần phải thực hiện một cách khác để khai báo một thể hiện mới của một đối tượng chung mà không phải lặp lại các tham số loại của nó ... toán tử kim cương.

Theo như ví dụ ban đầu của bạn List<String> list = new LinkedList(), trình biên dịch tạo cảnh báo cho phép gán đó bởi vì nó phải. Xem xét điều này:

List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);

Generics tồn tại để cung cấp bảo vệ thời gian biên dịch chống lại việc làm sai. Trong ví dụ trên, sử dụng loại thô có nghĩa là bạn không nhận được sự bảo vệ này và sẽ gặp lỗi khi chạy. Đây là lý do tại sao bạn không nên sử dụng các loại thô.

// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);

Tuy nhiên, toán tử kim cương cho phép phía bên phải của phép gán được xác định là một thể hiện chung thực sự với các tham số cùng loại với bên trái ... mà không phải nhập lại các tham số đó. Nó cho phép bạn giữ sự an toàn của thuốc generic với nỗ lực gần như tương tự như sử dụng loại thô.

Tôi nghĩ điều quan trọng cần hiểu là các loại thô (không có <>) không thể được đối xử giống như các loại chung. Khi bạn khai báo một loại thô, bạn sẽ không nhận được bất kỳ lợi ích và kiểm tra loại tổng quát nào. Bạn cũng phải nhớ rằng thuốc generic là một phần mục đích chung của ngôn ngữ Java ... chúng không chỉ áp dụng cho các nhà xây dựng không tranh cãi của Collections!


31
Khả năng tương thích ngược là rất tốt, nhưng không phải trả giá cho sự phức tạp. Tại sao Java 7 không thể giới thiệu -compatibilitychuyển đổi trình biên dịch trong khi nếu vắng mặt thì javacsẽ cấm tất cả các loại thô và chỉ thực thi các loại chung chung? Điều này sẽ làm cho mã của chúng tôi ít dài dòng hơn.
Rosdi Kasim

3
@Rosdi: Đồng ý, không cần loại thô trong mã mới. Tuy nhiên, tôi rất muốn bao gồm số phiên bản Java trong tệp nguồn (thay vì (mis) bằng cách sử dụng dòng lệnh), hãy xem câu trả lời của tôi.
maaartinus

37
Cá nhân tôi không thích việc sử dụng kim cương KHÔNG GIỚI HẠN bạn đang xác định VÀ bắt đầu trên cùng một dòng. List<String> strings = new List<>()Không sao, nhưng nếu bạn xác định private List<String> my list;, và sau đó xuống nửa trang bạn khởi tạo my_list = new List<>(), thì nó không hay! Danh sách của tôi chứa gì nữa? Oh, hãy để tôi săn lùng xung quanh cho định nghĩa. Đột nhiên, lợi ích của các phím tắt kim cương tạm biệt.
rmirabelle

11
@rmirabelle Nó khác với : my_list = getTheList()? Có một số cách tốt hơn để giải quyết loại vấn đề này: 1. sử dụng IDE hiển thị cho bạn các loại biến khi di chuột. 2. sử dụng tên biến có ý nghĩa hơn, chẳng hạn như private List<String> strings3. không phân tách khai báo và khởi tạo biến trừ khi bạn thực sự phải làm.
Natix

1
@Morfidon: Vâng, nó vẫn hợp lệ cho Java 8. Tôi khá chắc chắn rằng bạn sẽ nhận được cảnh báo.
ColinD

36

Sự hiểu biết của bạn hơi thiếu sót. Toán tử kim cương là một tính năng hay vì bạn không cần phải lặp lại. Thật hợp lý khi xác định loại một lần khi bạn khai báo loại nhưng không có nghĩa là xác định lại loại ở bên phải. Nguyên tắc DRY.

Bây giờ để giải thích tất cả các fuzz về việc xác định các loại. Bạn đúng rằng loại được loại bỏ trong thời gian chạy nhưng một khi bạn muốn lấy một cái gì đó ra khỏi Danh sách với định nghĩa loại, bạn sẽ lấy lại như loại bạn đã xác định khi khai báo danh sách nếu không nó sẽ mất tất cả các tính năng cụ thể và chỉ có Các tính năng đối tượng ngoại trừ khi bạn chuyển đối tượng được truy xuất về loại ban đầu, đôi khi có thể rất phức tạp và dẫn đến ClassCastException.

Sử dụng List<String> list = new LinkedList()sẽ giúp bạn có được cảnh báo rawtype.


8
List<String> list = new LinkedList()là mã chính xác. Bạn biết điều này và tôi cũng biết điều này. Và câu hỏi (theo tôi hiểu) là: tại sao chỉ có trình biên dịch java không hiểu rằng mã này khá an toàn?
La Mã

22
@Roman: List<String> list = new LinkedList()không mã đúng. Chắc chắn, nó sẽ tốt đẹp nếu nó được! Và có lẽ có thể là nếu Java có khái quát ngay từ đầu và không phải đối phó với khả năng tương thích ngược của các loại chung đã từng không chung chung, nhưng thực tế là như vậy.
ColinD

6
@ColinD Java thực sự không cần phải xử lý khả năng tương thích ngược trong từng dòng đơn . Trong bất kỳ tệp nguồn Java nào sử dụng tổng quát, các loại không chung chung cũ đều bị cấm (bạn luôn có thể sử dụng <?>nếu giao tiếp với mã kế thừa) và toán tử kim cương vô dụng không nên tồn tại.
maaartinus

16

Dòng này gây ra cảnh báo [không được kiểm tra]:

List<String> list = new LinkedList();

Vì vậy, câu hỏi biến đổi: tại sao cảnh báo [không được kiểm tra] không tự động bị chặn chỉ dành cho trường hợp khi bộ sưu tập mới được tạo?

Tôi nghĩ rằng, nó sẽ là nhiệm vụ khó khăn hơn nhiều sau đó thêm <>tính năng.

CẬP NHẬT : Tôi cũng nghĩ rằng sẽ có một mớ hỗn độn nếu sử dụng hợp pháp các loại thô 'chỉ cho một vài thứ'.


13

Về lý thuyết, toán tử kim cương cho phép bạn viết mã nhỏ gọn hơn (và có thể đọc được) bằng cách lưu các đối số kiểu lặp lại. Trong thực tế, đó chỉ là hai ký tự khó hiểu hơn cho bạn không có gì. Tại sao?

  1. Không có lập trình viên lành mạnh sử dụng các loại thô trong mã mới. Vì vậy, trình biên dịch có thể đơn giản giả định rằng bằng cách viết không có đối số kiểu nào bạn muốn nó suy ra chúng.
  2. Toán tử kim cương không cung cấp thông tin loại, nó chỉ nói trình biên dịch, "nó sẽ ổn thôi". Vì vậy, bằng cách bỏ qua nó, bạn có thể không làm hại. Tại bất kỳ nơi nào mà toán tử kim cương là hợp pháp, trình biên dịch có thể được "suy ra".

IMHO, có một cách rõ ràng và đơn giản để đánh dấu một nguồn là Java 7 sẽ hữu ích hơn là phát minh ra những thứ kỳ lạ như vậy. Trong mã được đánh dấu như vậy, các loại thô có thể bị cấm mà không mất gì.

Btw., Tôi không nghĩ rằng nó nên được thực hiện bằng cách sử dụng một công tắc biên dịch. Phiên bản Java của tệp chương trình là một thuộc tính của tệp, không có tùy chọn nào cả. Sử dụng một cái gì đó tầm thường như

package 7 com.example;

có thể làm cho nó rõ ràng (bạn có thể thích một cái gì đó tinh vi hơn bao gồm một hoặc nhiều từ khóa ưa thích). Nó thậm chí sẽ cho phép biên dịch các nguồn được viết cho các phiên bản Java khác nhau mà không gặp vấn đề gì. Nó sẽ cho phép giới thiệu các từ khóa mới (ví dụ: "mô-đun") hoặc loại bỏ một số tính năng lỗi thời (nhiều lớp không lồng nhau không công khai trong một tệp hoặc bất cứ điều gì) mà không mất bất kỳ khả năng tương thích nào.


2
Bạn đã xem xét sự khác biệt giữa new ArrayList(anotherList)new ArrayList<>(anotherList)(đặc biệt nếu nó được gán cho List<String>anotherListlà một List<Integer>)?
Paul Bellora

@Paul Bellora: Không có gì đáng ngạc nhiên đối với tôi, cả hai đều được biên dịch. Người có kim cương thậm chí không đưa ra cảnh báo. Tuy nhiên, tôi không thể thấy bất kỳ ý nghĩa nào trong việc này, bạn có thể giải thích?
maaartinus

Xin lỗi, tôi đã không giải thích rất tốt. Xem sự khác biệt giữa hai ví dụ sau: ideone.com/uyHaghideone.com/ANkg3T Tôi chỉ nêu ra rằng việc sử dụng toán tử kim cương thay vì loại thô, ít nhất là khi các đối số có giới hạn chung được thông qua trong.
Paul Bellora 17/03/13

Thật ra tôi đã không dành thời gian để đọc câu trả lời của ColinD - anh ấy trích dẫn khá nhiều điều tương tự.
Paul Bellora 17/03/13

2
Vì vậy, nếu chúng ta sắp giới thiệu một cú pháp mới cho các kiểu thô, đối với một vài nơi thực sự cần thiết, tại sao không sử dụng một cái gì đó như thế new @RawType List(). Đó là các chú thích kiểu và cú pháp Java 8 hợp lệ cho phép sử dụng nó ở mọi nơi khi cần, vd @RawType List = (@RawType List) genericMethod();. Xem xét rằng các loại thô hiện đang tạo cảnh báo trình biên dịch trừ khi @SuppressWarningsđã đặt thích hợp , @RawTypesẽ là một sự thay thế hợp lý và không cần một cú pháp tinh tế hơn.
Holger

8

Khi bạn viết List<String> list = new LinkedList();, trình biên dịch sẽ tạo ra một cảnh báo "không được kiểm tra". Bạn có thể bỏ qua nó, nhưng nếu bạn thường bỏ qua những cảnh báo này, bạn cũng có thể bỏ lỡ một cảnh báo thông báo cho bạn về một vấn đề an toàn loại thực.

Vì vậy, tốt hơn là viết một mã không tạo ra các cảnh báo bổ sung và toán tử kim cương cho phép bạn thực hiện theo cách thuận tiện mà không cần lặp lại không cần thiết.


4

Tất cả cho biết trong các phản hồi khác là hợp lệ nhưng các trường hợp sử dụng không hoàn toàn hợp lệ IMHO. Nếu một người kiểm tra Guava và đặc biệt là các bộ sưu tập liên quan đến công cụ, điều tương tự đã được thực hiện với các phương thức tĩnh. Ví dụ: Lists.newArrayList () cho phép bạn viết

List<String> names = Lists.newArrayList();

hoặc với nhập tĩnh

import static com.google.common.collect.Lists.*;
...
List<String> names = newArrayList();
List<String> names = newArrayList("one", "two", "three");

Quả ổi có các tính năng rất mạnh khác như thế này và tôi thực sự không thể nghĩ ra nhiều cách sử dụng cho <>.

Sẽ hữu ích hơn nếu họ định biến hành vi của toán tử kim cương thành mặc định, nghĩa là kiểu được suy ra từ phía bên trái của biểu thức hoặc nếu kiểu bên trái được suy ra từ phía bên phải. Cái sau là những gì xảy ra trong Scala.


3

Điểm cho toán tử kim cương chỉ đơn giản là giảm việc gõ mã khi khai báo các kiểu chung. Nó không có bất kỳ ảnh hưởng nào đến thời gian chạy.

Sự khác biệt duy nhất nếu bạn chỉ định trong Java 5 và 6,

List<String> list = new ArrayList();

là bạn phải chỉ định @SuppressWarnings("unchecked")cho list(nếu không bạn sẽ nhận được cảnh báo truyền không được kiểm tra). Hiểu biết của tôi là nhà điều hành kim cương đang cố gắng để phát triển dễ dàng hơn. Không có gì để làm trong thời gian chạy thực thi chung chung.


bạn thậm chí không phải sử dụng chú thích đó. Ít nhất là trong Eclipse, bạn chỉ cần nói với trình biên dịch không cảnh báo bạn về điều này và bạn vẫn ổn ...
Xerus

Tốt hơn là nên có chú thích. Không phải mọi nhà phát triển đều sử dụng Eclipse ở đây.
Buhake Sindi
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.