Về một lỗi của Java 8 Stream API
Bài đăng này đã không được cập nhật trong 7 năm
Tháng này mình xin giới thiệu về một lỗi nhỏ của Stream API trong Java 8 mới được giới thiệu trên Dzone. Và một số tip nhỏ trong lập trình với Java.
1. Stream API không làm việc được với Sublist của ArrayList
Stream API trong Java 8 hỗ trợ việc đánh giá kiểu Lazy. Điều đó có nghĩa là những hoạt động (xử lý) chung gian không có tác dụng cho đến khi có sự hoạt động kiểu đầu cuối. Chúng ta xét ví dụ sau.
public static void main (String args[]) {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
Stream stream =
ints.stream()
.peek(System.out::println)
.filter(i -> i % 2 == 0);
}
Nếu chúng ta chạy đoạn code trên sẽ không cho ra kế quả gì. Hàm peek
và filter
sẽ không được đánh gía cho đến khi chúng ta thêm vào nó một hoạt động kiểu đầu cuối như forEach
.
public static void main (String args[]) {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
Stream stream =
ints.stream();
ints.add(4);
ints.add(5);
stream.foreach(System.out::println);
}
Chương trình trên hoạt động tốt và cho ra kết quả là in toàn bộ các phần tử của list kiểu Integer.
Giờ ta tạo một sublist từ list trên, tạo một stream từ sublist và thêm vài phần tử cho sublist mới tạo.
public static void main (String args[]) {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.add(4);
ints.add(5);
List<Integer> ints2 = ints.subList(0, 3);
Stream stream = ints2.stream();
ints2.add(4);
ints2.add(5);
stream.foreach(System.out::println);
}
Giờ nếu chúng ta chạy đoạn code trên sẽ bắn ra exception là ConcurrentModificationException
.
Lỗi này đã được Oracal fix ở phiên bản Java 9 sắp release.
2. Sử dụng Enums trong tham số method
Mẹo sử dụng Enums cho tham số đẻ chỉ hành vi phương pháp. Ta xét ví dụ sau, hàm sắp xếp đơn giản.
void sort() { //1
// do some thing
}
Hàm trên còn hoạt động tốt chừng nào ta chưa muốn thêm yêu cầu cho nó. Giờ ta thêm yêu cầu là sắp xem theo thứ tự giảm hoặc không.
void sort(Boolean descending) { //2
// do some thing
}
có nghĩa là khi descending == true
thì hàm được yêu cầu sắp xế theo thứ tự giảm dần. Ngược lại (giá trị khác true) thì sắp xếp ngẫu nhiên hoặc tăng dần.
Giờ ta yêu cầu thêm là hàm phả cung cấp những tùy chọn sắp xếp thật cụ thể (tăng, giảm, ngẫu nhiên) thì sao?
void sort(int flag) { //3
// flat: 0-ngẫu nhiên, 1-giảm, 2-tăng
// do some thing
}
Cả ba ví dụ trên về mặt logic đều ổn song, về mặt readable code thì không được tốt. Các tham số và giá trị của chúng không mang nhiều ý nghĩa. Giờ ta sử dụng Enums cho tham số để nó mang ý nghĩa trực quan hơn.
enum Order {
ASC, DESC, UNSPECIFIED, RANDOM;
}
void sort(Order order) { //4
// do some thing
}
3. Lọa bỏ việc viết overloading method bằng Lambdas
Trong Java nếu một Interface có hai hay nhiều method có cùng tên và sử dụng tham số khác nhau là các Functional interface, điều này sẽ tạo ra sự mơ hồ ở phía client. Ví dụ Interface Point
có hai method là add(Function<Point, String> renderer)
và add(Predicate<Point> logCondition)
. Khi ta gọi point.add(p -> p + "lambda")
thì trình biên dịch sẽ báo lỗi vì nó không biết method nào thực sự được gọi.
Khắc phục điều này ta gắn tên tham số vào tên hàm, như vậy tên hàm sẽ khác nhau và cũng hàm ý nghĩa nhiều hơn.
public interface Point {
addRenderer(Function<Point, String> renderer);
addLogCondition(Predicate<Point> logCondition);
}
4. Sử dụng RemoveIf trong Collections
Trong Java khi ta muốn loại bỏ những item của danh sách thỏa mãn điều kiện loại bỏ thông thường ta duyện từng phần từ của danh sách và kiểm tra nếu thỏa mãn điều kiện loại thì gọi hàm remove()
để loại bỏ phần tử đó khỏi danh sách.
Phương pháp "truyền thống".
for (Iterator it = items.iterator(); it.hasNext();) {
if (predicate(it.next())) {
it.remove();
}
}
Java 8 ta nên dùng method removeIf()
sẽ đơn giản hơn rất nhiều.
items.removeIf(i -> predicate(i));
Tài liệu tham khảo
All rights reserved