Những mã xấu mà Java 8 có thể khử
Bài viết được sự cho phép của Nguyễn Bình Sơn
Tới hiện tại, Java 8 đã được sử dụng trên hầu hết các ứng dụng chạy trên JVM, nhưng điều đó không có nghĩa là những gì tối tân của phiên bản này đã được khai thác triệt để. Dưới đây là một số cách viết mã già cỗi nên được cập nhật.
1. Inner class vô danh
Bất cứ khi nào bạn gặp một inner class vô danh, bạn nên cân nhắc sử dụng biểu thức lambda. Lấy ví dụ:
list.sort(new Comparator() public int compare (String o1, String o2) { return o1.length() - o2.length(); } });
…chuyển thành như sau ngắn gọn hơn nhiều:
[java]list.sort((o1,o2) -> o1.length() - o2.length());
Dù vậy, mã ở đây vẫn không quá dễ đọc, chúng ta sẽ tiếp tục xem xét về Comparator.
2. Comparators
Comparator trong Java 8 không chỉ tối tân ở sự tương thích với các biểu thức lambda. Nó có những phương thức kết hợp với các tham chiếu phương thức, giúp cho mã trong sáng hơn rất nhiều:
[java]list.sort((o1,o2) -> o1.length() - o2.length());
Một ví dụ khác, bạn thậm chí có thể lấy các kết quả theo thứ tự ngược lại, tất cả chỉ bằng cách sử dụng thêm một phương thức hỗ trợ khác:
[java]list.sort((o1,o2) -> o1.length() - o2.length());
3. Các class không trạng thái
Thường thì bạn sẽ hay gặp các lớp không có gì khác ngoài các phương thức tĩnh (chúng thường được đặt tên được tận cùng là “Util” hay “Helper”), chúng có nhiệm vụ gom nhóm các phương thức lại trong một không gian tên duy nhất. Không ai cần phải tạo đối tượng của các class này, và kể cả khi có làm việc đó thì các đối tượng này cũng không lưu giữ bất cứ dữ liệu riêng nào, do đó các class này được gọi là class không trạng thái (stateless).
Với Java 8, các interface có khả năng chứa các phương thức tĩnh, và trở thành lựa chọn tốt hơn so với class, bởi chúng ta không phải lo có ai đó tạo đối tượng của interface. Ví dụ kinh điển lần nữa lại là Comparator – với các phương thức tĩnh hữu dụng và mạnh mẽ của nó.
Tương tự như thế, nếu bạn gặp một class trừu tượng không trạng thái, chỉ có duy nhất những phương thức trừu tượng được thiết kế để ghi đè, lớp đó có thể được chuyển đổi thành những FunctionalInterface và sau đó được implement bằng biểu thức lambda, như Runnable chẳng hạn:
Trước Java 8:
new Thread(new Runnable() { @Override public void run() { System.out.println("New thread created"); } }).start();
Java 8:
new Thread(() -> { System.out.println("New thread created"); }).start();
Một ví dụ khác để thử xem xét cách triển khai, sử dụng annotation @FunctionalInterface cho interface:
@FunctionalInterface interface Square { int calculate(int x); }
… và implement bằng biểu thức lambda:
int a = 5; Square s = (x) -> x * x; int ans = s.calculate(a); System.out.println(ans);
4. Các chỉ dẫn lặp và rẽ nhánh lồng nhau
Chúng ta có bộ API Streams được thiết kế để truy cập các collection theo cách vô cùng uyển chuyển. Khi gặp mã nguồn như dưới đây:
List<Field> validFields = new ArrayList<Field>(); for (Field field : fields) { if (meetsCriteria(field)) { validFields.add(field); } } return validFields;
…bạn phải nghĩ tới việc thay thế bằng mã sử dụng Streams. Ở đây ta dùng filter và collect:
return fields.stream() .filter(this::meetsCriteria) .collect(Collectors.toList());
Đôi khi, các vòng lặp với một lệnh rẽ nhánh ở trong có thể được tái cấu trúc thành anyMatch hay findFirst:
for (String current : strings) { if (current.equals(wanted)) { return true; } } return false;
…có thể được thay thế bởi:
return strings.stream() .anyMatch(current -> current.equals(wanted)); Và: for (String current : strings) { if (current.equals(wanted)) { return current; } } return null;
…có thể sửa thành:
return strings.stream() .filter(current -> current.equals(wanted)) .findFirst() .orElse(null);
Lưu ý rằng orElse(null) thực tế là một mã xấu, chúng ta sẽ nói đến sau.
5. Đa thao tác trên collection
Mặc dù đã cố gắng tối ưu hóa, nhưng chúng ta vẫn thường xuyên gặp trường hợp một chuỗi các thao tác được thực hiện trên một hay nhiều collection để có được kết quả mong muốn. Xem xét ví dụ dưới đây:
// collect messages for logging List<LogLine> lines = new ArrayList<>(); for (Message message : messages) { lines.add(new LogLine(message)); } // sort Collections.sort(lines); // log them for (LogLine line : lines) { line.log(LOG); }
Chia để trị đã giúp cho mã được rõ ràng, nhưng chưa nói tới việc phải sử dụng tới comment để làm rõ ý, thì lời gọi Collections.sort vẫn gợi ý rằng chúng ta có thể sử dụng Streams tại đây. Trong thực tế, toàn bộ đoạn mã trên có thể được gộp vào một stream duy nhất:
messages.stream() .map(LogLine::new) .sorted() .forEach(logLine -> logLine.log(LOG));
Tái cấu trúc này không chỉ giúp mã dễ đọc hơn hay ít lại một biến trung gian, mà thực tế còn có hiệu năng cao hơn.
6. Duyệt để xóa bỏ các phần tử
Mã trước-Java-8 có thể có những đoạn mã như sau:
Iterator<String> iterator = strings.iterator(); while (iterator.hasNext()) { String current = iterator.next(); if (current.endsWith("foo bar")) { iterator.remove(); } }
Giờ đây đoạn mã trên có thể được rút ngắn còn một dòng:
[java]list.sort((o1,o2) -> o1.length() - o2.length());[java]list.sort((o1,o2) -> o1.length() - o2.length());
Ngắn hơn, dễ đọc hơn, và cũng nhanh hơn!
7. Kiểm Null
NullPointerExceptions là điểm chí tử của các nhà phát triển Java, và bạn sẽ không lấy làm lạ khi các phép kiểm null nằm rải rác trong mã. Java 8 cho chúng ta API Optional giúp chúng ta mô tả kết quả trả về tốt hơn nhiều cũng như loại bỏ các kiểm null không cần thiết. Hãy quay lại với orElse mà chúng ta có ở tái cấu trúc số 4:
public static String findString (String wanted){ List<String> strings = new ArrayList<>(); return strings.stream() .filter(current -> current.equals(wanted)) .findFirst() .orElse(null); }
Bất kỳ lời gọi findString nào cũng sẽ phải kiểm null cho giá trị nhận được, và nếu an toàn thì làm một thao tác nào đó:
String foundString = findString(wantedString); if (foundString == null) { return "Did not find value" + wantedString; } else { return foundString; }
Mã này xấu, dễ lặp và tẻ nhạt. Nếu cập nhật phương thức findString thành sử dụng Optional:
public static Optional<String> findString(String wanted) { List<String> strings = new ArrayList<>(); return strings.stream() .filter(current -> current.equals(wanted)) .findFirst(); }
…thì chúng ta có thể thoát khỏi trường hợp không có giá trị một cách thanh nhã hơn nhiều:
[java]list.sort((o1,o2) -> o1.length() - o2.length());
Bài viết gốc được đăng tải tại Tạp Chí Lập Trình
Có thể bạn quan tâm:
- TẤT TẦN TẬT VỀ JAVA
- Học lập trình Java từ đâu và như thế nào?
- Cách JavaScript hoạt động: quản lý vùng nhớ + 4 cách giải quyết vấn đề thất thoát vùng nhớ
Xem thêm các việc làm Java hấp dẫn tại TopDev
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- i iOS 18 có gì mới? Có nên cập nhật iOS 18 cho iPhone của bạn?
- G Gamma AI là gì? Cách tạo slide chuyên nghiệp chỉ trong vài phút
- P Power BI là gì? Vì sao doanh nghiệp nên sử dụng PBI?
- K KICC HCMC x TOPDEV – Bước đệm nâng tầm sự nghiệp cho nhân tài IT Việt Nam
- T Trello là gì? Cách sử dụng Trello để quản lý công việc
- T TOP 10 SỰ KIỆN CÔNG NGHỆ THƯỜNG NIÊN KHÔNG NÊN BỎ LỠ
- T Tìm hiểu Laptop AI – So sánh Laptop AI với Laptop thường
- M MySQL vs MS SQL Server: Phân biệt hai RDBMS phổ biến nhất
- S SearchGPT là gì? Công cụ tìm kiếm mới có thể đánh bại Google?