YOMEDIA
ADSENSE
Tìm hiểu Java Collections Framework
179
lượt xem 40
download
lượt xem 40
download
Download
Vui lòng tải xuống để xem tài liệu đầy đủ
Khung cộng tác của các sưu tập Java (Java Collections Framework) là rộng lớn. Có rất nhiều lớp và giao diện trong khung cộng tác. Tại đây, chúng ta sẽ trình bày nhiều hơn, dù không phải là tất cả về chúng.
AMBIENT/
Chủ đề:
Bình luận(0) Đăng nhập để gửi bình luận!
Nội dung Text: Tìm hiểu Java Collections Framework
- ìm hiểu Java Collections Framework Trả lời Liên hệ Khung cộng tác của các sưu tập Java (Java Collections Framework) là rộng lớn. Có r ất nhi ều l ớp và giao di ện trong khung cộng tác. Tại đây, chúng ta sẽ trình bày nhiều hơn, dù không phải là tất cả về chúng. Các giao diện và các lớp sưu tập Khung công tác của các sưu tập Java dựa trên triển khai thực hiện cụ thể một số giao di ện định nghĩa các ki ểu sưu tập (collection): Giao diện List định nghĩa một sưu tập các phần tử Object có thể dẫn hướng. • Giao diện Set định nghĩa một sưu tập không có các phần tử trùng lặp. • Giao diện Map định nghĩa một sưu tập các cặp khóa - giá trị. • Chúng ta sẽ nói về một vài triển khai thực hiện cụ thể trong hướng dẫn này. Đây không phải là một danh sách đầy đ ủ, nhưng nhiều khả năng bạn thường xuyên thấy những thứ sau đây trong các dự án phát triển bằng ngôn ngữ Java: Giao diện Các triển khai thực hiện List ArrayList, Vector Set HashSet, TreeSet Map HashMap Tất cả các giao diện trong khung cộng tác, trừ Map là các giao di ện con của giao di ện Collection, trong đó đ ịnh nghĩa c ấu trúc chung nhất của một sưu tập. Mỗi sưu tập gồm nhiều phần tử. Với vai trò là trình thực hi ện các giao di ện con c ủa Collection, tất cả kiểu sưu tập chia sẻ chung (theo trực giác) một số hành vi: Các phương thức để mô tả kích thước của sưu tập (như size() và isEmpty()). • Các phương thức để mô tả nội dung của sưu tập (như contains() và containsAll()). • Các phương thức để hỗ trợ thao tác về nội dung của sưu tập (như add(), remove() và clear()). • Các phương thức để cho phép bạn chuyển đổi một sưu tập thành một mảng (như toArray()). • Một phương thức để cho phép bạn nhận được một trình vòng lặp (iterator) trên mảng các phần tử (iterator()). • Chúng ta sẽ nói về một số phương thức trên trong phần này. Đồng thời chúng ta sẽ thảo luận trình vòng l ặp (iterator) là gì và cách sử dụng nó như thế nào. Lưu ý rằng các Map là đặc biệt. Thật sự chúng hoàn toàn không là một sưu tập. Tuy nhiên, chúng có hành vi r ất gi ống các sưu tập, vì vậy chúng ta cũng nói về chúng trong phần này. Các triển khai thực hiện Danh sách (List) Các phiên bản cũ hơn của JDK chứa một lớp được gọi là Vector. Nó vẫn còn có trong các phiên bản mới hơn, nhưng bạn chỉ nên sử dụng nó khi bạn cần có một sưu tập đồng bộ hoá -- đó là, một trong những yếu tố là an toàn phân luồng. (Nói về phân luồng đã vượt ra ngoài phạm vi của bài viết này, chúng ta sẽ thảo luận ngắn gọn về khái ni ệm ấy trong ph ần Tóm tắt). Trong các trường hợp khác, bạn nên sử dụng lớp ArrayList. Bạn vẫn có thể sử dụng Vector, nhưng nó áp đặt một số chi phí thêm mà bạn thường không cần. Một ArrayList là cái như tên của nó gợi ý: danh sách các phần tử theo thứ tự. Chúng ta đã thấy làm thế nào đ ể tạo ra một danh sách và làm thế nào để thêm các phần tử vào nó, trong bài hướng dẫn gi ới thi ệu trước. Khi chúng ta tạo ra một l ớp Wallet lồng trong trong hướng dẫn này, chúng ta đã tích hợp vào đó một ArrayList để giữ các hoá đơn thanh toán của Adult: Phương thức getMoneyTotal() sử dụng một trình vòng lặp (iterator) để duyệt qua danh sách các hoá đ ơn thanh toán và tính tổng giá trị của chúng. Một Iterator tương tự như một Enumeration trong các phiên bản cũ hơn của ngôn ngữ Java. Khi b ạn
- nhận được một trình vòng lặp trên sưu tập (bằng cách gọi iterator()), trình vòng l ặp cho phép bạn duyệt qua (traverse) toàn bộ sưu tập bằng cách sử dụng một số phương thức quan trọng, được minh họa trong mã lệnh ở trên: hasNext() cho bạn biết còn có một phần tử tiếp theo khác trong sưu tập không. next() cho bạn phần tử tiếp theo đó. Như chúng ta đã thảo luận ở trên, bạn phải ép kiểu đúng khi bạn trích ra các phần tử từ sưu tập khi sử dụng next(). Tuy nhiên, Iterator còn cho chúng ta một số khả năng bổ sung thêm. Chúng ta có thể loại bỏ các ph ần t ử kh ỏi l ớp ArrayList bằng cách gọi remove() (hay removeAll(), hay clear()), nhưng chúng ta cũng có thể sử dụng Iterator đ ể làm đi ều đó. Hãy thêm một phương thức rất đơn giản được gọi là spendMoney() tới Adult: Phương thức này gọi removeBill() trên Wallet: Chúng ta nhận được một Iterator trên các hoá đơn thanh toán ArrayList, và duyệt qua danh sách đ ể tìm một k ết quả kh ớp với giá trị hóa đơn được chuyển qua (aBill). Nếu chúng ta tìm thấy một kết quả khớp, chúng ta gọi remove() trên trình vòng lặp để loại bỏ hóa đơn đó. Cũng đơn giản, nhưng còn chưa phải là đơn giản hết mức. Mã dưới đây thực hi ện cùng một công việc và dễ đọc hơn nhiều: Có thể bạn sẽ không thường xuyên gọi remove() trên một Iterator nhưng sẽ rất tốt nếu có công cụ đó khi bạn cần nó. Lúc này, chúng ta có thể loại bỏ chỉ một hóa đơn riêng lẻ mỗi lần khỏi Wallet. Sẽ là tốt hơn nếu sử dụng sức mạnh của một List để giúp chúng ta loại bỏ nhiều hóa đơn cùng một lúc, như sau: Chúng ta cần phải thêm removeBills() vào wallet của chúng ta đ ể thực hi ện vi ệc này. Hãy th ử mã d ưới đây: Đây là việc triển khai thực hiện dễ dàng nhất mà chúng ta có thể sử dụng. Chúng ta gọi removeAll() trên List các hoá đ ơn của chúng ta, chuyển qua một Collection. Sau đó phương thức này loại bỏ tất cả các phần tử khỏi danh sách có trong Collection. Hãy thử chạy mã dưới đây: Các kết quả không phải là những gì mà chúng ta muốn. Chúng ta đã kết thúc mà không còn hóa đ ơn nào trong ví cả. Tại sao? Bởi vì removeAll() loại bỏ tất cả các kết quả khớp. Nói cách khác, bất kỳ và t ất c ả các k ết quả kh ớp v ới m ột m ục trong List mà chúng ta chuyển cho phương thức đều bị loại bỏ. Các hoá đ ơn thanh toán mà chúng ta đã chuyển cho ph ương thức có chứa 1 và 2. Ví của chúng ta có chứa hai số 1 và một số 2. Khi removeAll() tìm ki ếm kết quả kh ớp v ới ph ần t ử số 1, nó tìm thấy hai kết quả khớp và loại bỏ chúng cả hai. Đó không phải là những gì mà chúng ta muốn! Chúng ta c ần thay đổi mã của chúng ta trong removeBills() để sửa lại điều này: Mã này chỉ loại bỏ một kết quả khớp riêng rẽ, chứ không phải là tất cả các kết quả khớp. Nhớ cẩn thận với removeAll(). Triển khai thực hiện tập hợp Có hai triển khai thực hiện Tập hợp (Set) thường được sử dụng phổ biến: HashSet, không đảm bảo thứ tự vòng lặp. • TreeSet, bảo đảm thứ tự vòng lặp. • Các tài liệu hướng dẫn ngôn ngữ Java gợi ý rằng bạn sẽ đi đến chỗ sử dụng triển khai thực hiện thứ nhất trong hầu hết
- các trường hợp. Nói chung, nếu bạn cần phải chắc chắn rằng các phần tử trong Set của bạn xếp theo một thứ tự nhất định nào đó khi bạn duyệt qua nó bằng một trình vòng lặp, thì hãy sử dụng triển khai thực hi ện thứ hai. Nếu không, sử dụng cách thứ nhất. Thứ tự của các phần tử trong một TreeSet (có thực hiện giao diện SortedSet) đ ược gọi là thứ tự tự nhiên (natural ordering); điều này có nghĩa là, hầu hết mọi trường hợp, bạn sẽ có khả năng sắp x ếp các ph ần t ử d ựa trên phép so sánh equals(). Giả sử rằng mỗi Adult có một tập hợp các biệt hiệu. Chúng ta thực sự không quan tâm đ ến chúng được sắp đặt thế nào, nhưng các bản sao sẽ không có ý nghĩa. Chúng ta có thể sử dụng một HashSet đ ể l ưu gi ữ chúng. Trước tiên, chúng ta thêm một biến cá thể: Sau đó chúng ta thêm một phương thức để thêm biệt hiệu vào Set: Bây giờ hãy thử chạy mã này: Bạn sẽ thấy chỉ có một Bobby đơn lẻ xuất hiện trên màn hình. Các triển khai thực hiện Map Map (Ánh xạ) là một tập hợp các cặp khóa - giá trị. Nó không thể chứa các khóa gi ống hệt nhau. Mỗi khóa ph ải ánh x ạ t ới một giá trị đơn lẻ, nhưng giá trị đó có thể là bất kỳ kiểu gì. Bạn có thể nghĩ về một ánh xạ như là List có đặt tên. Hãy tưởng tượng một List trong đó mỗi phần tử có một tên mà bạn có thể sử dụng để trích ra phần tử đó trực tiếp. Khóa có thể là bất cứ cái gì kiểu Object, giống như giá trị. Một lần nữa, điều đó có nghĩa là bạn không thể l ưu trữ các giá tr ị ki ểu nguyên thủy (primitive) trực tiếp vào trong một Map (bạn có ghét các giá trị ki ểu nguyên thủy không đ ấy ?). Thay vào đó, bạn phải sử dụng các lớp bao gói kiểu nguyên thủy để lưu giữ các giá trị đó. Mặc dù đây là một chiến lược tài chính mạo hiểm, chúng ta sẽ cung cấp cho mỗi Adult một tập hợp các thẻ tín d ụng đ ơn giản nhất có thể chấp nhận được. Mỗi thẻ sẽ có một tên và một số dư (ban đầu là 0). Trước tiên, chúng ta thêm một bi ến cá thể: Sau đó chung ta thêm một phương thức để bổ sung thêm một thẻ tín dụng (CreditCard)t ới Map: Giao diện của Map khác với các giao diện của các sưu tập khác. Bạn gọi put() với một khóa và một giá tr ị đ ể thêm một mục vào ánh xạ. Bạn gọi get() với khóa để trích ra một giá trị. Chúng ta sẽ làm vi ệc này trong một ph ương th ức đ ể hi ển thị số dư của một thẻ: Tất cả những gì còn lại là thêm phương thức charge() để cho phép cộng thêm vào số dư của chúng ta: Bây giờ hãy thử chạy mã dưới đây, nó sẽ hiển thị cho bạn 19.95 trên màn hình. Một thẻ tín dụng điển hình có một tên, một số tài khoản, một hạn mức tín dụng và một số dư. Mỗi mục trong một Map
- chỉ có thể có một khóa và một giá trị. Các thẻ tín dụng rất đơn giản của chúng ta rất phù hợp, bởi vì chúng ch ỉ có một tên và một số dư hiện tại. Chúng ta có thể làm cho phức tạp hơn bằng cách tạo ra một l ớp được gọi là CreditCard, v ới các biến cá thể dành cho tất cả các đặc tính của một thẻ tín dụng, sau đó lưu trữ các cá thể của l ớp này như các giá trị cho các mục trong Map của chúng ta. Có một số khía cạnh thú vị khác về giao diện Map để trình bày trước khi chúng ta đi ti ếp (đây không phải là một danh sách đầy đủ): Phương thức Hành vi Trả lời Map có chứa khóa đã cho hay không. containsKey() Trả lời Map có chứa giá trị đã cho hay không. containsValue() Trả về một Set tập hợp các khóa. keySet() Trả về một Set tập hợp các giá trị. values() Trả về một Set tập hợp các cặp khóa - giá trị, được định nghĩa như là các cá thể của các Map.Entry. entrySet() Cho phép bạn loại bỏ giá trị cho một khóa đã cho. remove() Trả lời Map có rỗng không (rỗng có nghĩa là, không chứa khóa nào). isEmpty() Một số trong các phương thức này, chẳng hạn như isEmpty() chỉ là để cho tiện thôi, nhưng một số là rất quan tr ọng. Ví dụ, cách duy nhất để thực hiện vòng lặp qua các phần tử trong một Map là thông qua một trong các t ập h ợp có liên quan (tập hợp các khóa, các giá trị, hoặc các cặp khóa-giá trị). Lớp Collections Khi bạn đang sử dụng khung cộng tác các sưu tập Java, bạn cần phải nắm được những gì có sẵn trong l ớp Collections. Lớp này gồm có một kho lưu trữ các phương thức tĩnh để hỗ trợ các thao tác trên sưu tập. Chúng tôi sẽ không trình bày tất cả chúng ở đây, bởi vì bạn có thể tự mình đọc API, nhưng chúng tôi sẽ trình bày hai phương thức thường xuyên xuất hi ện trong mã Java: • copy() • sort() Phương thức đầu tiên cho phép bạn sao chép các nội dung của một sưu tập này tới một sưu tập khác, như sau: Mã này sao chép từ nguồn (source) vào đích (target). Đích phải có cùng kích th ước nh ư ngu ồn, vì th ế b ạn không th ể sao chép một List vào một List rỗng. Phương thức sort() sắp xếp các phần tử theo thứ tự tự nhiên của chúng. Tất cả các phần tử phải triển khai thực hi ện giao diện Comparable sao cho chúng có thể so sánh với nhau. Các lớp có sẵn gi ống như String đã thực hi ện đi ều này. Vì v ậy, đối với một tập hợp các chuỗi ký tự, chúng ta có thể sắp xếp chúng theo thứ tự tăng dẫn theo ki ểu biên soạn từ đi ển bằng mã sau đây: Bạn sẽ nhận được [four, one, three, two] trên màn hình. Nhưng bạn có thể sắp xếp các lớp mà bạn tạo ra như thế nào? Chúng ta có thể làm điều này cho Adult. Trước tiên, chúng ta làm cho l ớp Adult có thể so sánh l ẫn nhau: Sau đó, chúng ta ghi đè compareTo() để so sánh hai cá thể Adult Chúng ta sẽ duy trì vi ệc so sánh r ất đ ơn gi ản đ ể làm ví d ụ,
- do đó nó làm rất ít việc: Bất kỳ số nào nhỏ hơn 0 có nghĩa là "bé hơn", và -1 là giá trị thích hợp đ ể sử dụng. Tương tự, 1 là thuận ti ện để dành cho "lớn hơn". Như bạn có thể thấy, 0 có nghĩa là "bằng nhau". So sánh hai đối tượng theo cách này rõ ràng là một quá trình thủ công. Bạn cần phải đi qua các biến cá thể và so sánh từng biến. Trong trường hợp này, chúng ta so sánh tên và h ọ và sắp xếp thực tế theo họ. Nhưng bạn nên biết, tại sao ví dụ của chúng ta lại rất đơn giản. Mỗi Adult có nhi ều hơn là ch ỉ tên và họ. Nếu chúng ta muốn làm một phép so sánh sâu hơn, chúng ta sẽ phải so sánh các Wallet c ủa mỗi Adult đ ể xem xem chúng có bằng nhau không, nghĩa là chúng ta sẽ phải triển khai thực hi ện compareTo() trên Wallet và ph ần còn l ại. Ngoài ra, để thật chính xác khi so sánh, bất cứ khi nào bạn ghi đè compareTo(), bạn cần phải ch ắc ch ắn là phép so sánh là tương thích với equals(). Chúng ta không triển khai thực hiện equals(), vì thế chúng ta không lo l ắng v ề vi ệc t ương thích với nó, nhưng chúng ta có thể phải làm. Trong thực tế, tôi đã thấy mã có bao gồm một dòng như sau, tr ước khi tr ả v ề EQUAL: Cách tiếp cận khác để so sánh các đối tượng là trích thuật toán trong compareTo() vào một đối tượng có ki ểu Trình so sánh (Comparator), sau đó gọi Collections.sort() với sưu tập cần sắp xếp và Comparator, nh ư sau: Bạn sẽ thấy "Al Jones" và "Bob Smith", theo thứ tự đó, trong cửa sổ màn hình của bạn. Có một số lý do thích đáng để sử dụng cách tiếp cận thứ hai. Các lý do kỹ thuật vượt ra ngoài phạm vi của hướng dẫn này. Tuy nhiên, từ viễn cảnh của phát triển hướng đối tượng, đây có thể là một ý tưởng tốt khi tách bi ệt phần mã so sánh vào trong đối tượng khác, hơn là cung cấp cho mỗi Adult khả năng tự so sánh với nhau. Tuy nhiên, vì đây th ực s ự là nh ững gì mà equals() thực hiện, mặc dù kết quả là toán tử boolean, có các l ập luận thích hợp ủng hộ cho c ả hai cách ti ếp c ận. Using collections Khi nào bạn nên sử dụng một kiểu sưu tập cụ thể ? Đó là một phán xét cần đến năng lực của bạn, và chính vì th ế mà b ạn hy vọng sẽ được trả lương hậu hĩ khi là một lập trình viên. Bất chấp những gì mà nhiều chuyên gia tin tưởng, có rất ít các quy tắc chắc chắn và nhanh chóng đ ể xác đ ịnh c ần sử dụng những lớp nào trong một tình huống đã cho nào đó. Theo kinh nghi ệm cá nhân của tôi, trong ph ần l ớn các l ần khi s ử dụng các sưu tập, một ArrayList hoặc một HashMap (hãy nhớ, một Map không thật sự là một sưu tập) đ ều bị chơi khăm. Rất có khả năng, bạn cũng từng có trải nghiệm như vậy. Dưới đây là một số quy tắc ngón tay cái, một số là hi ển nhiên hơn những cái còn lại: Khi bạn nghĩ rằng mình cần có một sưu tập, hãy bắt đầu với một List, sau đó cứ đ ể cho các mã sẽ báo cho bạn • biết có cần một kiểu khác không. Nếu bạn chỉ cần nhóm các thứ gì đó, hãy sử dụng một Set. • Nếu thứ tự trong vòng lặp là rất quan trọng khi duyệt qua một sưu tập, hãy sử dụng Tree... một hương vị khác • của sưu tập, khi ở đó có sẵn. Tránh sử dụng Vector, trừ khi bạn cần khả năng đồng bộ hóa của nó. • Không nên lo lắng về việc tối ưu hóa cho đến khi (và trừ khi) hiệu năng trở thành một vấn đ ề. • Các bộ sưu tập là một trong những khía cạnh mạnh mẽ nhất của ngôn ngữ Java. Đừng ngại khi sử dụng chúng, nhưng cần cảnh giác về các vụ "Tìm ra rồi" (gotchas). Ví dụ, có một cách thuận ti ện đ ể chuyển đ ổi từ một Array thành một ArrayList: Mã này đưa ra một UnsupportedOperationException, vì List đ ược Arrays.asList() tr ả v ề là không thay đ ổi đ ược. Bạn không thể thêm một phần tử mới vào một List không thay đổi. Hãy để ý. Theo IBM Việt Nam
ADSENSE
CÓ THỂ BẠN MUỐN DOWNLOAD
Thêm tài liệu vào bộ sưu tập có sẵn:
Báo xấu
LAVA
AANETWORK
TRỢ GIÚP
HỖ TRỢ KHÁCH HÀNG
Chịu trách nhiệm nội dung:
Nguyễn Công Hà - Giám đốc Công ty TNHH TÀI LIỆU TRỰC TUYẾN VI NA
LIÊN HỆ
Địa chỉ: P402, 54A Nơ Trang Long, Phường 14, Q.Bình Thạnh, TP.HCM
Hotline: 093 303 0098
Email: support@tailieu.vn